﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
using Asol.Reporting.Support;
using Asol.Reporting.Support.Components;

namespace Asol.Reporting.Green.Components.Commands
{
    public class CommandPanel : Asol.Reporting.Editor.Components.DblGraphPanel
    {
        #region Konstrukce, Dispose
        public CommandPanel()
        {

            Vector v;

            v = new Vector(-500d, 20d);
            v = new Vector(new PointF(86.602540f, 50f));           // 30°
            v = new Vector(new PointF(-50f, 86.602540f));          // 120°
            v = new Vector(new PointF(-96.592583f, -25.881905f));  // 195°
            v = new Vector(new PointF(0f, -100f));                 // 270°
            v = new Vector(new PointF(96.592583f, -25.881905f));   // 345°

            this.DataInit();
            this.SurfaceInit();
            this.MouseEventsInit();
            this.LayoutEventsInit();
            this.PaintInit();
            this.ToolTipInit();
            this._TestFill();
        }
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            this.SurfaceDispose();
        }
        #endregion
        #region Data = Grupy a Commandy (přidávání a odebírání grup a commandů)
        protected void DataInit()
        {
            this._Groups = new CommandGroups(this);
            this.ContentIsPrepared = false;
        }
        public void AddGroup(string name)
        {
            CommandGroup group = new CommandGroup(name, name);
            this.AddGroup(group);
        }
        public void AddGroup(string name, string text)
        {
            CommandGroup group = new CommandGroup(name, text);
            this.AddGroup(group);
        }
        public void AddGroup(CommandGroup group)
        {
            _Groups.Add(group);
        }
        /// <summary>
        /// Vrátí grupu daného jména.
        /// Pokud by neexistovala, založí ji.
        /// </summary>
        /// <param name="groupName"></param>
        /// <returns></returns>
        internal CommandGroup GetGroup(string groupName)
        {
            CommandGroup group;
            string key = (groupName == null ? "" : groupName);
            if (!this._Groups.TryFindByName(key, out group))
            {
                group = new CommandGroup(key, key);
                this._Groups.Add(group);
            }
            return group;
        }
        /// <summary>
        /// Smaže všechny grupy a tím i všechny commandy
        /// </summary>
        public void ClearGroups()
        {
            this._Groups.Clear();
        }
        /// <summary>
        /// Přidá dodaný command do aktuální grupy (posledně přidané), nebo bez grupy (pokud ještě žádná nebyla).
        /// Commandy bez grupy se zobrazují na začátku panelu.
        /// </summary>
        /// <param name="button"></param>
        public void AddCommand(CommandButton button)
        {
            CommandItem item = new CommandItem(button);
            this._AddCommand(item, null);
        }
        /// <summary>
        /// Přidá dodaný command do grupy daného jména.
        /// Pokud taková grupa neexistuje, dojde k chybě.
        /// </summary>
        /// <param name="button"></param>
        /// <param name="groupName"></param>
        public void AddCommand(CommandButton button, string groupName)
        {
            CommandItem commandItem = new CommandItem(button);
            this._AddCommand(commandItem, groupName);
        }
        /// <summary>
        /// Přidá button do grupy daného jména
        /// </summary>
        /// <param name="commandItem"></param>
        /// <param name="groupName"></param>
        private void _AddCommand(CommandItem commandItem, string groupName)
        {
            CommandGroup group = this.GetGroup(groupName);
            group.AddItem(commandItem);
        }
        /// <summary>
        /// Grupy v panelu. Přidávat a odebírat lze přes metody panelu AddGroup a RemoveGroup.
        /// </summary>
        public IEnumerable<CommandGroup> Groups { get { return this._Groups; } }
        /// <summary>
        /// Kolekce grup
        /// </summary>
        internal CommandGroups _Groups { get; set; }
        #endregion
        #region Surface - pohyblivé oblasti (klouzavé povrchy pro zobrazení řady Group headerů a Commandů)
        /// <summary>
        /// Iniciace pracovních povrchů (GroupSurface a ItemSurface)
        /// </summary>
        protected void SurfaceInit()
        {
            this.GroupSurface = new CommandSurface(this);
            this.ItemSurface = new CommandSurface(this);
        }
        /// <summary>
        /// Připraví pracovní plochy (GroupSurface a ItemSurface) před zahájením přípravy jednotlivých grup a itemů. 
        /// Volá se na začátku kreslení v procesu příprav.
        /// </summary>
        protected void SurfaceClear()
        {
            this.GroupSurface.Clear();
            this.ItemSurface.Clear();
        }
        /// <summary>
        /// Nyní určí vizuální velikost pracovních ploch (GroupSurface a ItemSurface) 
        /// jednak podle aktuální velikosti controlu a orientace, 
        /// a jednak podle rozměru virtuální plochy (při horizontální orientaci převezmu virtuální výšku, a naopak).
        /// </summary>
        protected void SurfacePrepareActualArea()
        {
            int b = 5;                                     // Okraje (Boční pro horizontální orientaci, Horní a dolní pro vertikální orientaci)
            int s = 1;                                     // Horní a střední a dolní mezera
            Rectangle groupArea, itemArea;
            float angle = 0f;
            if (this.Orientation == System.Windows.Forms.Orientation.Horizontal)
            {
                int w = this.Width - 2 * b;
                groupArea = new Rectangle(b, s, w, this.GroupSurface.VirtualOccupiedArea.Height);
                itemArea = new Rectangle(b, groupArea.Bottom, w, this.ItemSurface.VirtualOccupiedArea.Height);
                angle = 90f;
            }
            else
            {
                int h = this.Height - 2 * b;
                groupArea = new Rectangle(s, b, this.GroupSurface.VirtualOccupiedArea.Width, h);
                itemArea = new Rectangle(groupArea.Right, b, this.ItemSurface.VirtualOccupiedArea.Width, h);
                angle = 0f;
            }
            this.GroupSurface.SetActualArea(groupArea);
            this.ItemSurface.SetActualArea(itemArea);
            this.ActualBackgroundAngle = angle;
        }
        /// <summary>
        /// Vykreslí podklad pracovních ploch (GroupSurface a ItemSurface) = pozadí
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="renderData"></param>
        private void SurfacePaintBack(Graphics graphics, PaintRenderData renderData)
        {
            this.GroupSurface.PaintActual(graphics);
            this.ItemSurface.PaintActual(graphics);
        }
        /// <summary>
        /// Vykreslí scroll buttony pracovních ploch (GroupSurface a ItemSurface)
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="renderData"></param>
        private void SurfacePaintScroll(Graphics graphics, PaintRenderData renderData)
        {
            this.GroupSurface.ScrollItemsPaintRender(graphics, renderData);
            this.ItemSurface.ScrollItemsPaintRender(graphics, renderData);
        }
        private bool SurfaceSkipMouseDown(MouseEventArgs e)
        {
            if (this.GroupSurface.ActualVisibleArea.Contains(e.Location)) return this.GroupSurface.IsMouseDownStopMoving(e.Location);
            if (this.ItemSurface.ActualVisibleArea.Contains(e.Location)) return this.ItemSurface.IsMouseDownStopMoving(e.Location);
            return false;
        }
        /// <summary>
        /// Disposuje Surfaces
        /// </summary>
        private void SurfaceDispose()
        {
            if (this.GroupSurface != null)
                ((IDisposable)this.GroupSurface).Dispose();
            this.GroupSurface = null;
            if (this.ItemSurface != null)
                ((IDisposable)this.ItemSurface).Dispose();
            this.ItemSurface = null;
        }
        /// <summary>
        /// Controler prostoru Group (záhlaví)
        /// </summary>
        internal CommandSurface GroupSurface { get; private set; }
        /// <summary>
        /// Controler prostoru Itemů (tlačítka)
        /// </summary>
        internal CommandSurface ItemSurface { get; private set; }
        #endregion
        #region Layout - size, dock, Orientation
        private void LayoutEventsInit()
        {
            this.DockChanged += new EventHandler(DockChangedHandler);
            this.SizeChanged += new EventHandler(SizeChangedHandler);
        }
        private void SizeChangedHandler(object sender, EventArgs e)
        {
            this.DetectOrientation();
            this.ContentIsPrepared = false;
        }
        private void DockChangedHandler(object sender, EventArgs e)
        {
            this.DetectOrientation();
            this.ContentIsPrepared = false;
        }
        protected void DetectOrientation()
        {
            switch (this.Dock)
            {
                case DockStyle.Top:
                case DockStyle.Bottom:
                    this.Orientation = System.Windows.Forms.Orientation.Horizontal;
                    break;
                case DockStyle.Left:
                case DockStyle.Right:
                    this.Orientation = System.Windows.Forms.Orientation.Vertical;
                    break;
                default:
                    this.Orientation = (this.Width > this.Height ? System.Windows.Forms.Orientation.Horizontal : System.Windows.Forms.Orientation.Vertical);
                    break;
            }
        }
        /// <summary>
        /// Aktuální orientace panelu a tedy i ikon
        /// </summary>
        public Orientation Orientation { get; private set; }
        #endregion
        #region Interaktivita
        protected void MouseEventsInit()
        {
            this.MouseEnter += new EventHandler(MouseEnterHandler);
            this.MouseLeave += new EventHandler(MouseLeaveHandler);
            
            this.MouseClick += new MouseEventHandler(MouseClickHandler);
            this.MouseHover += new EventHandler(MouseHoverHandler);
            this.MouseMove += new MouseEventHandler(MouseMoveHandler);
            this.MouseDown += new MouseEventHandler(MouseDownHandler);
            this.MouseUp += new MouseEventHandler(MouseUpHandler);
            this.MouseWheel += new MouseEventHandler(MouseWheelHandler);
        }
        private void MouseEnterHandler(object sender, EventArgs e)
        {
            if (IsActiveItemChanged(null, ref this.ActiveItemOnMouse))
                this.Draw();
        }
        private void MouseLeaveHandler(object sender, EventArgs e)
        {
            if (IsActiveItemChanged(null, ref this.ActiveItemOnMouse))
                this.Draw();
        }
        private void MouseMoveHandler(object sender, MouseEventArgs e)
        {
            if (this.ActiveItemMouseDown == null)
                this.MouseMoveNone(e);
            else
                this.MouseMoveDown(e);
        }
        private void MouseHoverHandler(object sender, EventArgs e)
        {
            if (this.ActiveItemOnMouse != null)
            {
                string toolTipTitle = this.ActiveItemOnMouse.ToolTipTitle;
                string toolTipText = this.ActiveItemOnMouse.ToolTipText;
                this.ToolTipShow(toolTipTitle, toolTipText, this.ActiveItemOnMouse.MousePoint.Value.Add(-45, 15));
            }
        }
        private void MouseDownHandler(object sender, MouseEventArgs e)
        {
            this.ToolTipHide();
            if (this.MouseDownSkip(e)) return;
            bool change = IsActiveItemChanged(_FindActiveItem(e), ref this.ActiveItemOnMouse);
            this.ActiveItemMouseDown = this.ActiveItemOnMouse;
            this.ActiveItemMouseDown.State = PaintState.Pressed;
            this.ActiveItemOnMouse = null;
            this.Draw();
        }
        private void MouseUpHandler(object sender, MouseEventArgs e)
        {
            if (this.ActiveItemMouseDown == null) return;
            this.MouseUpEnd(e);
            IsActiveItemChanged(this.ActiveItemMouseDown, ref this.ActiveItemOnMouse);
            IsActiveItemChanged(_FindActiveItem(e), ref this.ActiveItemOnMouse);
            this.ActiveItemMouseDown = null;
            this.Draw();
        }
        private void MouseWheelHandler(object sender, MouseEventArgs e)
        {
            
        }
        private void MouseClickHandler(object sender, MouseEventArgs e)
        {
            
        }
        private void MouseMoveNone(MouseEventArgs e)
        {
            if (IsActiveItemChanged(_FindActiveItem(e), ref this.ActiveItemOnMouse))
            {
                this.ToolTipHide();
                this.Draw();
            }
        }
        private void MouseMoveDown(MouseEventArgs e)
        {
            if (this.ActiveItemMouseDown == null) return;                     // Nemám info o objektu pod myší
            if (!this.ActiveItemMouseDown.MousePoint.HasValue) return;        // Nemám info o výchozí souřadnici myši
            if (this.ActiveItemMouseDown.MouseIsInFence)
            {
                if (this.ActiveItemMouseDown.MouseFence.Value.Contains(e.Location)) return;     // Myš je stále v ohrádce MouseFence => nic neděláme...
                this.ActiveItemMouseDown.MouseFence = null;                   // Myš vyběhla z ohrádky, tak to tu ohrádku už můžeme zahodit. Myš se "pohnula" a my jsme v režimu Pohyb.
                this.ActiveItemMouseDown.MouseDragBegin(this.ActiveItemMouseDown.MousePoint.Value, e.Location);
            }
            this.ActiveItemMouseDown.MouseDragRun(this.ActiveItemMouseDown.MousePoint.Value, e.Location);
            this.DrawIfNeed();
        }
        private void MouseUpEnd(MouseEventArgs e)
        {
            if (this.ActiveItemMouseDown == null || !this.ActiveItemMouseDown.MouseButton.HasValue) return;

            if (!this.ActiveItemMouseDown.MouseIsInFence)
                this.ActiveItemMouseDown.MouseDragEnd(this.ActiveItemMouseDown.MousePoint.Value, e.Location);
            else if (this.ActiveItemMouseDown.MouseButton.Value == System.Windows.Forms.MouseButtons.Left)
                this.ActiveItemMouseDown.MouseLeftClick();
            else if (this.ActiveItemMouseDown.MouseButton.Value == System.Windows.Forms.MouseButtons.Right)
                this.ActiveItemMouseDown.MouseRightClick();
        }
        private bool IsActiveItemChanged(ActiveItemInfo current, ref ActiveItemInfo target)
        {
            bool change = !ActiveItemInfo.IsEqual(current, target);

            if (target != null)
                target.State = PaintState.Standard;
            target = current;
            if (target != null)
                target.State = PaintState.HotMouse;

            return change;
        }
        /// <summary>
        /// Vrátí objekt popisující prvek na dané souřadnici
        /// </summary>
        /// <param name="mousePoint"></param>
        /// <returns></returns>
        private ActiveItemInfo _FindActiveItem(MouseEventArgs mouseArgs)
        {
            if (this.ActiveItemList != null)
            {
                for (int i = this.ActiveItemList.Count - 1; i >= 0; i--)
                {   // Aktivní prvky procházíme od konce, tak jak jsou viditelné na Z-ose:
                    ActiveItem item = this.ActiveItemList[i];
                    if (item.ActualVisibleArea.Contains(mouseArgs.Location))
                        return new ActiveItemInfo(this, mouseArgs, item);
                }
            }
            return new ActiveItemInfo(this, mouseArgs, null);;
        }
        private ActiveItemInfo ActiveItemOnMouse;
        private ActiveItemInfo ActiveItemMouseDown;
        #region class ActiveItemInfo : informace o aktuální pozici a o prvku pod myší
        internal class ActiveItemInfo
        {
            public ActiveItemInfo(CommandPanel panel, MouseEventArgs mouseArgs, ActiveItem activeItem)
            {
                this.Panel = panel;
                
                this.ActiveItem = activeItem;
                this.ItemType = ActiveItemType.None;
                if (activeItem is CommandGroup)
                    this.ItemType = ActiveItemType.GroupHeader;
                else if (activeItem is CommandGroupArea)
                    this.ItemType = ActiveItemType.GroupArea;
                else if (activeItem is CommandItem)
                    this.ItemType = ActiveItemType.CommandItem;
                else if (activeItem != null)
                    this.ItemType = ActiveItemType.ActiveItem;

                if (mouseArgs != null)
                {
                    this.MousePoint = mouseArgs.Location;
                    this.MouseFence = mouseArgs.Location.CreateRectangleFromCenter(6);
                    this.MouseButton = mouseArgs.Button;
                }
                this.Time = DateTime.Now;
            }
            /// <summary>
            /// Reference na panel
            /// </summary>
            internal CommandPanel Panel { get; private set; }
            /// <summary>
            /// Bod myši
            /// </summary>
            public Point? MousePoint { get; private set; }
            /// <summary>
            /// Prostor myši, kde se její pohyb nepovažuje za pohyb
            /// </summary>
            public Rectangle? MouseFence { get; set; }
            /// <summary>
            /// true: myš je v ohrádce (ještě se nepohnula)
            /// </summary>
            public bool MouseIsInFence { get { return (this.MouseFence.HasValue); } }
            /// <summary>
            /// Stisknuté buttony na myši
            /// </summary>
            public MouseButtons? MouseButton { get; private set; }
            /// <summary>
            /// Čas získání objektu = prvotní událost (kvůli doubleclicku atd)
            /// </summary>
            public DateTime Time { get; private set; }

            /// <summary>
            /// Typ aktivního prvku
            /// </summary>
            public ActiveItemType ItemType { get; private set; }
            /// <summary>
            /// Aktivní prvek
            /// </summary>
            public ActiveItem ActiveItem { get; private set; }
            public bool IsEmpty { get { return (this.ActiveItem == null); } }

            /// <summary>
            /// Vrátí true, pokud dva pointery na aktivní objektu ukazují na tenýž objekt (nebo společně neukazují nikam).
            /// Vrací false, pokud dva pointery nejsou shodné, tedy když dochází ke změně aktivního objektu.
            /// </summary>
            /// <param name="info1"></param>
            /// <param name="info2"></param>
            /// <returns></returns>
            internal static bool IsEqual(ActiveItemInfo info1, ActiveItemInfo info2)
            {
                if (info1 == null && info2 == null) return true;         // 2x null : to je stejné
                if (info1 == null || info2 == null) return false;        // 1x null : to není stejné

                if (info1.ItemType != info2.ItemType) return false;      // Jiný typ: to není stejné
                if (!Object.ReferenceEquals(info1.ActiveItem, info2.ActiveItem)) return false;  // Stejný typ, ale jiná instance: to je jiné
                return true;        // Stejný typ a stejná instance: to je totéž.
            }
            /// <summary>
            /// Stav aktivního objektu
            /// </summary>
            internal PaintState State { get { return (this.IsEmpty ? PaintState.None : this.ActiveItem.State); } set { if (!this.IsEmpty) this.ActiveItem.State = value; } }
            /// <summary>
            /// Titulek tooltip okna
            /// </summary>
            internal string ToolTipTitle { get { return (this.IsEmpty ? "" : this.ActiveItem.ToolTipTitle); } set { if (!this.IsEmpty) this.ActiveItem.ToolTipTitle = value; } }
            /// <summary>
            /// Text tooltip okna
            /// </summary>
            internal string ToolTipText { get { return (this.IsEmpty ? "" : this.ActiveItem.ToolTipText); } set { if (!this.IsEmpty) this.ActiveItem.ToolTipText = value; } }

            #region Interaktivita (adapter)
            internal void MouseDragBegin(Point origin, Point current)
            {
                if (!this.IsEmpty)
                    this.ActiveItem.MouseDragBegin(origin, current);
            }
            internal void MouseDragRun(Point origin, Point current)
            {
                if (!this.IsEmpty)
                    this.ActiveItem.MouseDragMove(origin, current);
            }
            internal void MouseDragEnd(Point origin, Point target)
            {
                if (!this.IsEmpty)
                    this.ActiveItem.MouseDragEnd(origin, target);
            }
            internal void MouseLeftClick()
            {
                if (!this.IsEmpty)
                    this.ActiveItem.MouseLeftClick();
            }
            internal void MouseRightClick()
            {
                if (!this.IsEmpty)
                    this.ActiveItem.MouseRightClick();
            }
            #endregion
        }
        internal enum ActiveItemType
        {
            None = 0,
            GroupHeader,
            GroupArea,
            CommandItem,
            ActiveItem
        }
        #endregion
        /// <summary>
        /// Soupis aktuálně viditelných prvků
        /// </summary>
        internal List<ActiveItem> ActiveItemList { get; private set; }
        /// <summary>Ignorovat událost MouseDown na dané souřadnici?</summary>
        protected bool MouseDownSkip(MouseEventArgs e)
        {
            return this.SurfaceSkipMouseDown(e);
        }
        #endregion
        #region Kreslení
        /// <summary>
        /// Iniciace kreslícího systému
        /// </summary>
        protected void PaintInit()
        {
            this.ItemSize = 36;
            this.ItemBorder = 1;
            this.ContentIsPainted = false;
        }
        /// <summary>
        /// Provede kreslení, když je to zapotřebí
        /// </summary>
        internal void DrawIfNeed()
        {
            if (!this.ContentIsPrepared || !this.ContentIsPainted)
                this.Draw();
        }
        /// <summary>
        /// Řízení kreslení
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected override void OnPaintToBuffer(object sender, PaintEventArgs e)
        {
            base.OnPaintToBuffer(sender, e);
            this.PaintPrepare(false);
            this.PaintRender(e.Graphics);
            this.ContentIsPainted = true;
        }
        #region Příprava dat před vykreslováním
        /// <summary>
        /// Provede přípravu před kreslením, pokud je toho zapotřebí.
        /// </summary>
        protected void PaintPrepare(bool force)
        {
            if (this.ContentIsPrepared && !force) return;

            this.SurfaceClear();
            this.PaintPrepareActual();
            this.PaintPrepareContent();
            this.SurfacePrepareActualArea();

            this.ContentIsPrepared = true;
        }
        /// <summary>
        /// Připraví data pro kreslení mimo grup a itemů a surface, tedy například splitter
        /// </summary>
        protected void PaintPrepareActual()
        { }
        /// <summary>
        /// Připraví obsah do obou surfaces (grupy a jejich itemy)
        /// </summary>
        protected void PaintPrepareContent()
        {
            PaintPrepareData panelData = new PaintPrepareData(this);
            foreach (CommandGroup group in this.Groups)
                group.PaintPrepare(panelData);
        }
        #endregion
        #region Vlastní kreslení
        /// <summary>
        /// Řídí vlastní vykreslování všech objektů
        /// </summary>
        /// <param name="graphics"></param>
        protected void PaintRender(Graphics graphics)
        {
            PaintRenderData renderData = new PaintRenderData(this);
            this.PaintActual(graphics, renderData);
            this.SurfacePaintBack(graphics, renderData);
            this.PaintRenderContent(graphics, renderData);
            this.SurfacePaintScroll(graphics, renderData);
            this.ActiveItemList = renderData.ActiveItemList;
        }

        /// <summary>
        /// Vykreslí sebe sama, ale nemusí se starat o pracovní povrchy (řeší tedy jen pasivní okolí), například splitter?
        /// </summary>
        /// <param name="graphics"></param>
        private void PaintActual(Graphics graphics, PaintRenderData renderData)
        {
        }
        /// <summary>
        /// Vykreslí obsah panelů (group a jejich items)
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="renderData"></param>
        private void PaintRenderContent(Graphics graphics, PaintRenderData renderData)
        {
            foreach (CommandGroup group in this.Groups)
                group.PaintRender(graphics, renderData);
        }
        /// <summary>
        /// Vykreslí pozadí pod itemy
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="bounds"></param>
        private void PaintActualBackgroudnItem(Graphics graphics, Rectangle bounds)
        {
            // Painter.PaintBackground(bounds, graphics, 0, Color.FromArgb(222, 184, 135), PaintState.Standard);
            using (SolidBrush brush = new SolidBrush(this.BackColor))
            {
                graphics.FillRectangle(brush, bounds);
            }
        }
        #endregion
        #region Data pro kreslení
        /// <summary>
        /// Metoda která požádá o překreslení controlu
        /// </summary>
        /// <param name="prepare"></param>
        /// <param name="draw"></param>
        /// <param name="immediately"></param>
        internal void RepaintRequest(bool prepare, bool draw, bool immediately)
        {
            if (prepare)
                this.ContentIsPrepared = false;
            if (draw)
                this.ContentIsPainted = false;
            if (immediately)
            {
                if (this.InvokeRequired)
                {
                    this.BeginInvoke(new Action(this.Draw));
                }
                else
                    this.Draw();
            }
        }
        /// <summary>
        /// true = Až to půjde, překresli se
        /// </summary>
        internal bool ContentIsPainted { get; private set; }
        /// <summary>
        /// true když data pro kreslení jsou připravena (v grupách a jejich itemech jsou připraveny jejich virtuální souřadnice)
        /// </summary>
        internal bool ContentIsPrepared { get; private set; }
        /// <summary>
        /// Orientace výplně, úhel
        /// </summary>
        internal float ActualBackgroundAngle { get; private set; }
        /// <summary>
        /// Velikost standardního itemu (některá z hodnot 16,24,32,48)
        /// </summary>
        internal int ItemSize { get; private set; }
        /// <summary>
        /// Okraj standardního itemu (běžně = 1)
        /// </summary>
        internal int ItemBorder { get; private set; }
        /// <summary>
        /// Obsahuje rozestup mezi prvky (=ItemSize + 2*ItemBorder + 2
        /// </summary>
        internal int ItemDistance { get { return this.ItemSize + 2 * this.ItemBorder + 2; } }
        #endregion
        #endregion
        #region Tooltip
        protected void ToolTipInit()
        {
            this.ToolTip = new ToolTip();
            this.ToolTip.Active = true;
            this.ToolTip.BackColor = SystemColors.Info;
            this.ToolTip.InitialDelay = 100;
            this.ToolTip.ToolTipIcon = ToolTipIcon.Info;
            this.ToolTip.UseFading = true;
        }
        protected void ToolTipShow(string title, string text, Point point)
        {
            this.ToolTip.ToolTipTitle = title;
            this.ToolTip.Show(text, this, point, 4500);
        }
        protected void ToolTipHide()
        {
            this.ToolTip.Hide(this);
        }

        protected ToolTip ToolTip { get; set; }
        #endregion
        #region Testovací náplň
        private void _TestFill()
        {
            string path = "Buttons";
            if (!System.IO.Directory.Exists(path)) return;

            List<string> files = new List<string>(System.IO.Directory.GetFiles(path));
            files.Sort();
            string extensions = ".bmp;.jpg;.jpeg;.png";
            int grpKey = 0;
            Color[] bcs = new Color[] { Color.Blue, Color.Green, Color.Red, Color.Yellow, Color.Violet };
            string groupName = "";
            foreach (string file in files)
            {
                string extn = System.IO.Path.GetExtension(file).ToLower();
                if (extensions.IndexOf(extn) < 0) continue;

                string itemName = System.IO.Path.GetFileNameWithoutExtension(file).ToLower();
                string rootName = itemName.Split('-', '_')[0];          // První název před pomlčkou nebo podtržítkem
                if (rootName != groupName)
                {
                    groupName = rootName;
                    CommandGroup group = this.GetGroup(groupName);
                    group.Text = rootName.ToUpper();
                    group.BackColorStandard = Color.LightGray;
                    group.BackColorHotMouse = bcs[grpKey % bcs.Length];
                    grpKey++;
                }

                CommandButton btn = new CommandButton();
                btn.Name = itemName;
                btn.ImageStandard = Bitmap.FromFile(file);
                btn.Text = System.IO.Path.GetFileNameWithoutExtension(file);
                btn.ToolTipTitle = "Ze souboru";
                btn.ToolTipText = file;
                btn.BackColorStandard = Color.Transparent;
                btn.BackColorHotMouse = Color.FromArgb(96, Color.Yellow);
                this.AddCommand(btn, groupName);
            }
            this.CheckToolTipInitialized();
        }
        #endregion
    }
    #region CommandGroups : kolekce skupin v command panelu, CommandGroup : jedna skupina
    /// <summary>
    /// CommandGroups : kolekce skupin v command panelu
    /// </summary>
    internal class CommandGroups : Asol.Reporting.Support.Data.Collection<CommandGroup>
    {
        internal CommandGroups(CommandPanel parent)
            : base(StringComparison.Ordinal)
        {
            this.Parent = parent;
        }
        protected override string GetName(CommandGroup item)
        {
            return item.Name;
        }
        internal CommandPanel Parent { get; private set; }
        protected override void OnItemAddBefore(CommandGroup item)
        {
            base.OnItemAddBefore(item);
            item.Parent = this;
        }
    }
    /// <summary>
    /// Grupa více buttonů
    /// </summary>
    public class CommandGroup : VirtualItem
    {
        #region Konstrukce
        public CommandGroup(string name)
        {
            this.Name = name;
            this.CommandItems = new CommandItems(this);
            this.CommandArea = new CommandGroupArea(this);
        }
        public CommandGroup(string name, string text)
        {
            this.Name = name;
            this.Text = text;
            this.CommandItems = new CommandItems(this);
            this.CommandArea = new CommandGroupArea(this);
        }
        /// <summary>
        /// Název skupiny používaný v aplikaci (stringová konstanta), nezobrazuje se, nesmí být prázdná ani duplicitní.
        /// Pořadí vkádání grup do command panelu odpovídá pořadí při jejich zobrazení.
        /// </summary>
        public string Name { get; private set; }
        /// <summary>
        /// Titulek skupiny v Command panelu, zobrazuje se pokud není prázdný. Může být prázdný, pak se položky skupiny zobrazují ale skupina nemá titulek.
        /// </summary>
        public string Text { get; set; }
        /// <summary>
        /// Kolekce položek commandů
        /// </summary>
        internal CommandItems CommandItems { get; set; }
        /// <summary>
        /// Aktivní prostor vyhrazený itemům (v liště tlačítek)
        /// </summary>
        internal CommandGroupArea CommandArea { get; set; }
        /// <summary>
        /// Přidá novou položku do této grupy
        /// </summary>
        /// <param name="commandItem"></param>
        internal void AddItem(CommandItem commandItem)
        {
            this.CommandItems.Add(commandItem);
        }
        /// <summary>
        /// true pokud je grupa uzavřená (je ve formě jedné ikony a rozbalovacího popup menu)
        /// </summary>
        internal bool Collapsed { get; set; }
        internal CommandGroups Parent { get; set; }
        internal CommandPanel Panel { get { return this.Parent.Parent; } }
        #endregion
        #region Kreslení grupy (záhlaví, prvky)
        /// <summary>
        /// Připraví svoje struktury na vykreslení, počínaje daným bodem
        /// </summary>
        /// <param name="point"></param>
        internal void PaintPrepare(PaintPrepareData panelData)
        {
            panelData.PrepareNewArea(0, 1);
            this.PaintPrepareGroup(panelData);
            this.PaintPrepareItems(panelData);
        }
        /// <summary>
        /// Připraví souřadnice pro hlavičku grupy
        /// </summary>
        /// <param name="groupData"></param>
        private void PaintPrepareGroup(PaintPrepareData panelData)
        {
            int length = (String.IsNullOrEmpty(this.Text) ? 0 : this.Text.Length * 12);
            Size size = (this.Panel.Orientation == Orientation.Horizontal ? new Size(length, 20) : new Size(20, length));
            Rectangle visibleArea, occupiedArea;
            panelData.AddGroupArea(size, 0, out visibleArea, out occupiedArea);
            this.SetVirtualArea(visibleArea, occupiedArea);
        }
        /// <summary>
        /// Připraví souřadnice pro jednotlivé prvky (buttony)
        /// </summary>
        /// <param name="panelData"></param>
        private void PaintPrepareItems(PaintPrepareData panelData)
        {
            foreach (CommandItem commandItem in this.CommandItems)
                commandItem.PaintPrepare(panelData);
            this.CommandArea.SetVirtualArea(panelData.CurrentItemVirtualArea, panelData.CurrentItemVirtualArea);
        }
        /// <summary>
        /// Vykreslí sebe a svoje itemy do dané grafiky.
        /// Co vykreslí a má být interaktivní, to vloží do seznamu v parametru activeItemList.
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="activeItemList"></param>
        internal void PaintRender(Graphics graphics, PaintRenderData renderData)
        {
            this.PaintRenderGroup(graphics, renderData);
            this.PaintRenderItems(graphics, renderData);
            graphics.ResetClip();
        }
        /// <summary>
        /// Vykreslí svoje záhlaví
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="renderData"></param>
        private void PaintRenderGroup(Graphics graphics, PaintRenderData renderData)
        {
            graphics.SetClip(renderData.GroupSurface.ActualVisibleArea);
            renderData.GroupSurface.CalculateActualArea(this);                 // Pracovní plocha skupin (renderData.GroupSurface) nechť přepočítá souřadnice pro prostor mého záhlaví (this) z virtuálních na vizuální.
            if (this.IsVisible)
            {
                if (this.BackColor.HasValue)
                    Painter.PaintBackground(this.ActualTotalArea, graphics, 0, Color.FromArgb(48, this.BackColor.Value), PaintState.Standard);
                FontStyle style = (this.AnyIsActive ? FontStyle.Bold : FontStyle.Regular);
                using (Font font = new Font(SystemFonts.DialogFont.Name, SystemFonts.DialogFont.Size, style))
                {
                    Painter.PaintLabel(graphics, this.Text, font, this.ActualTotalArea, Color.Black, ContentAlignment.MiddleCenter);
                }
                renderData.ActiveItemList.Add(this);                           // Já se přidám jakožto záhlaví do seznamu aktivních prvků
            }
        }
        /// <summary>
        /// Vykreslí svoje prvky (pozadí pod nimi, a jednotlivé prvky)
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="renderData"></param>
        private void PaintRenderItems(Graphics graphics, PaintRenderData renderData)
        {
            graphics.SetClip(renderData.ItemSurface.ActualVisibleArea);
            renderData.ItemSurface.CalculateActualArea(this.CommandArea);      // Pracovní plocha položek (renderData.ItemSurface) nechť přepočítá souřadnice pro prostor commandů (this.CommandArea) z virtuálních na vizuální.
            if (this.CommandArea.IsVisible)
            {
                this.PaintActual(graphics, renderData);
                foreach (CommandItem commandItem in this.CommandItems)
                    commandItem.PaintRender(graphics, renderData);
            }
        }
        /// <summary>
        /// Vykreslí 
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="renderData"></param>
        internal void PaintActual(Graphics graphics, PaintRenderData renderData)
        {
            if (this.BackColor.HasValue)
            {
                Color color = Color.FromArgb(64, this.BackColor.Value);
                Painter.PaintBackground(this.CommandArea.ActualTotalArea, graphics, 0, color, PaintState.Standard);
            }
            if (this.AnyIsActive)
            {
                Rectangle area = this.CommandArea.ActualTotalArea;
                area.Width--;
                area.Height--;
                graphics.DrawRectangle(Pens.Gray, area);
            }
            renderData.ActiveItemList.Add(this.CommandArea);             // Přidáme prostor buttonů do seznamu aktivních prvků
        }
        /// <summary>
        /// Kdokoliv z mé grupy je aktivní? (záhlaví, plocha, buttony)
        /// </summary>
        internal bool AnyIsActive { get { return (this.IsActive || this.CommandArea.IsActive || this.CommandItems.Any(ci => ci.IsActive)); } }
        #endregion
        #region Interaktivita
        public override void MouseDragBegin(Point origin, Point current)
        {
            this.Panel.GroupSurface.MouseDragBegin(origin, current);
        }
        public override void MouseDragMove(Point origin, Point current)
        {
            this.Panel.GroupSurface.MouseDragMove(origin, current);
        }
        public override void MouseDragEnd(Point origin, Point current)
        {
            this.Panel.GroupSurface.MouseDragEnd(origin, current);
        }
        #endregion
    }
    /// <summary>
    /// Třída zachycující prostor CommandGroup, v němž se vyskytují Itemy
    /// </summary>
    internal class CommandGroupArea : VirtualItem
    {
        public CommandGroupArea(CommandGroup owner)
        {
            this._Owner = owner;
        }
        private CommandGroup _Owner;
        internal CommandPanel Panel { get { return this._Owner.Panel; } }
        #region Interaktivita
        public override void MouseDragBegin(Point origin, Point current)
        {
            this.Panel.ItemSurface.MouseDragBegin(origin, current);
        }
        public override void MouseDragMove(Point origin, Point current)
        {
            this.Panel.ItemSurface.MouseDragMove(origin, current);
        }
        #endregion
    }
    #endregion
    #region CommandItems : kolekce položek v command panelu, CommandItem : obálka jednoho buttonu
    /// <summary>
    /// CommandItems : kolekce položek v command panelu
    /// </summary>
    internal class CommandItems : Asol.Reporting.Support.Data.Collection<CommandItem>
    {
        internal CommandItems(CommandGroup parent)
            : base(StringComparison.Ordinal)
        {
            this.Parent = parent;
        }
        protected override string GetName(CommandItem item)
        {
            return item.Name;
        }
        /// <summary>
        /// Parentem commandu je grupa commandů
        /// </summary>
        internal CommandGroup Parent { get; private set; }
        /// <summary>
        /// Reference na Command panel
        /// </summary>
        internal CommandPanel Panel { get { return this.Parent.Parent.Parent; } }
        protected override void OnItemAddBefore(CommandItem item)
        {
            base.OnItemAddBefore(item);
            item.Parent = this;
        }
    }
    /// <summary>
    /// Jedna fyzická položka v panelu (button, label, combo, cojávímco)
    /// </summary>
    internal class CommandItem : VirtualItem
    {
        #region Konstrukce a property
        internal CommandItem(CommandButton button)
        {
            this.Button = button;
            this.State = PaintState.Standard;
        }
        public string Name { get { return this.Button.Name; } }
        /// <summary>
        /// Parentem command itemu je kolekce commandů
        /// </summary>
        public CommandItems Parent { get; set; }
        /// <summary>
        /// Grupa, do níž command patří. Každý command patří do jedné grupy.
        /// </summary>
        public CommandGroup Group { get { return this.Parent.Parent; } }
        /// <summary>
        /// Reference na Command panel
        /// </summary>
        public CommandPanel Panel { get { return this.Parent.Parent.Parent.Parent; } }
        /// <summary>
        /// Data buttonu
        /// </summary>
        internal CommandButton Button { get; private set; }
        #endregion
        #region Podpora kreslení
        /// <summary>
        /// Připraví svoje struktury na vykreslení, počínaje daným bodem
        /// </summary>
        /// <param name="point"></param>
        internal void PaintPrepare(PaintPrepareData panelData)
        {
            Size size = new Size(this.Panel.ItemSize, this.Panel.ItemSize);
            Rectangle visibleArea, occupiedArea;
            panelData.AddItemArea(size, this.Panel.ItemBorder, out visibleArea, out occupiedArea);
            this.SetVirtualArea(visibleArea, occupiedArea);
        }
        /// <summary>
        /// Vypočítá svoji reálnou pozici (na základě své virtuální pozice this.ItemVirtualArea a dat v argumentu renderData),
        /// a podle potřeby sebe a svoje itemy vykreslí do dané grafiky.
        /// Co vykreslí a má být interaktivní, to vloží do seznamu v parametru activeItemList.
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="renderData"></param>
        internal void PaintRender(Graphics graphics, PaintRenderData renderData)
        {
            renderData.ItemSurface.CalculateActualArea(this);                  // Pracovní plocha položek (renderData.ItemSurface) nechť přepočítá souřadnice pro prostor tohoto buttonu (this) z virtuálních na vizuální.
            this.PaintRenderActual(graphics, renderData);
        }
        #endregion
        #region Podpora kreslení a interaktivity, virtual property napojené na datový button
        public override Color? BackColorStandard { get { return this.Button.BackColorStandard; } set { this.Button.BackColorStandard = value; } }
        public override Color? BackColorDisabled { get { return this.Button.BackColorDisabled; } set { this.Button.BackColorDisabled = value; } }
        public override Color? BackColorHotMouse { get { return this.Button.BackColorHotMouse; } set { this.Button.BackColorHotMouse = value; } }
        public override Color? BackColorFocused { get { return this.Button.BackColorFocused; } set { this.Button.BackColorFocused = value; } }
        public override Color? BackColorPressed { get { return this.Button.BackColorPressed; } set { this.Button.BackColorPressed = value; } }
        public override Image ImageStandard { get { return this.Button.ImageStandard; } set { this.Button.ImageStandard = value; } }
        public override Image ImageDisabled { get { return this.Button.ImageDisabled; } set { this.Button.ImageDisabled = value; } }
        public override Image ImageHotMouse { get { return this.Button.ImageHotMouse; } set { this.Button.ImageHotMouse = value; } }
        public override Image ImageFocused { get { return this.Button.ImageFocused; } set { this.Button.ImageFocused = value; } }
        public override Image ImagePressed { get { return this.Button.ImagePressed; } set { this.Button.ImagePressed = value; } }
        /// <summary>Titulek tooltipu</summary>
        public override string ToolTipTitle { get { return this.Button.ToolTipTitle; } set { this.Button.ToolTipTitle = value; } }
        /// <summary>Text tooltipu</summary>
        public override string ToolTipText { get { return this.Button.ToolTipText; } set { this.Button.ToolTipText = value; } }
        #endregion
        #region Interaktivita
        public override void MouseDragBegin(Point origin, Point current)
        {
            this.Panel.ItemSurface.MouseDragBegin(origin, current);
        }
        public override void MouseDragMove(Point origin, Point current)
        {
            this.Panel.ItemSurface.MouseDragMove(origin, current);
        }
        public override void MouseDragEnd(Point origin, Point current)
        {
            this.Panel.ItemSurface.MouseDragEnd(origin, current);
        }
        #endregion
    }
    #endregion
    #region CommandSurface : třída, která hraje roli podkladu pro virtuální prvky - na fyzické souřadnice promítá virtuální prvky
    /// <summary>
    /// VirtualSurface : třída, která hraje roli podkladu pro virutální prvky - na fyzické souřadnice promítá virtuální prvky
    /// </summary>
    public class CommandSurface : VirtualItem, IDisposable
    {
        #region Konstrukce a vstup souřadnic, základní kreslení
        public CommandSurface(CommandPanel owner)
        {
            this.Panel = owner;
            this.ScrollItemsInit();
            this._InertialMovementInit();
        }
        void IDisposable.Dispose()
        {
            this._InertialMovementDispose();
        }
        protected CommandPanel Panel { get; private set; }
        /// <summary>
        /// Orientace povrchu
        /// </summary>
        public Orientation Orientation { get { return this.Panel.Orientation; } }
        /// <summary>
        /// Virtuální souřadnice bodu, který je zobrazován na prvním vizuálním bodu (tj. na souřadnici 
        /// </summary>
        public Point FirstVirtualPoint { get { return this._FirstVirtualPoint; } set { this._FirstVirtualPoint = value; this.TransformOffsetPrepare(); } } private Point _FirstVirtualPoint;
        /// <summary>
        /// Nastaví vizuální souřadnice tohoto povrchu.
        /// </summary>
        /// <param name="surfaceActualArea"></param>
        internal void SetActualArea(Rectangle surfaceActualArea)
        {
            this.SetActualArea(surfaceActualArea, surfaceActualArea, surfaceActualArea);
        }
        internal override void SetActualArea(Rectangle actualArea, Rectangle occupiedArea, Rectangle visibleArea)
        {
            base.SetActualArea(actualArea, occupiedArea, visibleArea);
            this.TransformOffsetPrepare();                 // Po vložení vizuálních souřadnic připravíme transformační offset (případně posuneme první bod _FirstVirtualPoint)
            this.ScrollItemsPaintPrepare();                // Po vložení souřadnic určíme viditelnost scroll buttonů
        }
        internal override void SetVirtualArea(Rectangle visibleArea, Rectangle occupiedArea)
        {
            base.SetVirtualArea(visibleArea, occupiedArea);
            this.TransformOffsetPrepare();                 // Po vložení virtuálních souřadnic připravíme transformační offset (případně posuneme první bod _FirstVirtualPoint)
            this.ScrollItemsPaintPrepare();                // Po vložení virtuálních určíme viditelnost scroll buttonů
        }
        /// <summary>
        /// Vyprázdní všechny svoje souřadnice, volá se před začátkem příprav objektů, kdy jednotlivé prvky se budou umisťovat do instance této třídy.
        /// Neupravuje FirstVirtualPoint, ale nuluje ActualVisibleArea, ActualTotalArea, ActualOccupiedArea, VirtualOccupiedArea, VirtualVisibleArea.
        /// </summary>
        internal void Clear()
        {
            this.ActualVisibleArea = Rectangle.Empty;
            this.ActualTotalArea = Rectangle.Empty;
            this.ActualOccupiedArea = Rectangle.Empty;
            this.VirtualOccupiedArea = Rectangle.Empty;
            this.VirtualVisibleArea = Rectangle.Empty;
        }
        /// <summary>
        /// Vrátí souřadnice virtuálního bodu, který následuje za dosud obsazeným virtuálním prostorem (this.VirtualOccupiedArea) v ose dle orientace, s daným odstupem (space).
        /// </summary>
        /// <param name="space"></param>
        /// <returns></returns>
        internal Point GetVirtualNextPoint(int space)
        {
            return this.VirtualOccupiedArea.NextPoint(this.Orientation, space);
        }
        /// <summary>
        /// Daný prostor zařadí do this Virtual prostoru (do VirtualOccupiedArea, který v této třídě má význam reálně použitého prostoru.)
        /// </summary>
        /// <param name="occupiedArea"></param>
        internal void AddVirtualArea(Rectangle occupiedArea)
        {
            this.VirtualOccupiedArea = this.VirtualOccupiedArea.Enlarge(occupiedArea);
        }
        /// <summary>
        /// Vykreslení pozadí surface
        /// </summary>
        /// <param name="graphics"></param>
        internal override void PaintActual(Graphics graphics)
        {
            this.PaintActual(graphics, this.ActualVisibleArea);
        }
        /// <summary>
        /// Vykreslení pozadí surface do zadaných souřadnic
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="area"></param>
        internal void PaintActual(Graphics graphics, Rectangle area)
        {
            // Background:
            Color? backColor = this.BackColor;
            if (backColor.HasValue)
            {
                //Painter.PaintBackground(this.ActualTotalArea, graphics, 0, BackColor.Value, this.State);
                using (SolidBrush brush = new SolidBrush(this.BackColor.Value))
                {
                    graphics.FillRectangle(brush, area);
                }
            }
        }
        #endregion
        #region Scroll buttony
        /// <summary>
        /// Provede inicializaci scroll buttonů
        /// </summary>
        private void ScrollItemsInit()
        {
            this.ScrollItemPrev = new ActiveItem();
            this.ScrollItemPrev.MouseLeftClickEvent += new EventHandler(ScrollItemPrev_MouseLeftClickEvent);
            this.ScrollItemPrev.BackColorStandard = Color.FromArgb(190, Color.LightGray);
            this.ScrollItemPrev.BackColorHotMouse = Color.FromArgb(236, Color.LightGoldenrodYellow);
            this.ScrollItemNext = new ActiveItem();
            this.ScrollItemNext.MouseLeftClickEvent += new EventHandler(ScrollItemNext_MouseLeftClickEvent);
            this.ScrollItemNext.BackColorStandard = Color.FromArgb(190, Color.LightGray);
            this.ScrollItemNext.BackColorHotMouse = Color.FromArgb(236, Color.LightGoldenrodYellow);
        }
        void ScrollItemPrev_MouseLeftClickEvent(object sender, EventArgs e)
        {
            this._InertialMovementAdd(-1);
        }
        void ScrollItemNext_MouseLeftClickEvent(object sender, EventArgs e)
        {
            this._InertialMovementAdd(1);
        }
        /// <summary>
        /// Připraví scroll buttony, podle aktuální potřeby
        /// </summary>
        private void ScrollItemsPaintPrepare()
        {
            this.ScrollItemPrev.Hide();
            this.ScrollItemNext.Hide();
            if (this.Orientation == System.Windows.Forms.Orientation.Horizontal)
                this.ScrollItemsPaintPrepareH();
            else
                this.ScrollItemsPaintPrepareV();
        }
        /// <summary>
        /// Připraví scroll buttony, podle aktuální potřeby, pro orientaci Horizontal
        /// </summary>
        private void ScrollItemsPaintPrepareH()
        {
            Rectangle occupiedArea, actualArea;
            Rectangle area = this.ActualVisibleArea;
            int height = area.Height;
            int width = height / 2;
            int b = this.Panel.ItemBorder;

            if (this.VirtualVisibleArea.X > this.VirtualOccupiedArea.X)
            {   // "Doleva" je vidět, když virtuální zobrazovaný prostor je vlevo větší než počátek celého virtuálního prostoru (což bývá 0):
                occupiedArea = new Rectangle(area.X, area.Y, width, height);
                actualArea = occupiedArea.Change(0, b, -b, -b);
                this.ScrollItemPrev.SetActualArea(actualArea, occupiedArea, area);
                this.ScrollItemPrev.ImageStandard = IconLibrary.ArrowBlueLeft24;
                this.ScrollItemPrev.ImageHotMouse = IconLibrary.ArrowVioletLeft24;
            }

            if (this.VirtualVisibleArea.Right < this.VirtualOccupiedArea.Right)
            {   // "Doprava" je vidět, když virtuální zobrazovaný prostor je vpravo menší než je pravý konec celého virtuálního prostoru:
                occupiedArea = new Rectangle(area.Right - width, area.Y, width, height);
                actualArea = occupiedArea.Change(b, b, 0, -b);
                this.ScrollItemNext.SetActualArea(actualArea, occupiedArea, area);
                this.ScrollItemNext.ImageStandard = IconLibrary.ArrowBlueRight24;
                this.ScrollItemNext.ImageHotMouse = IconLibrary.ArrowVioletRight24;
            }
        }
        /// <summary>
        /// Připraví scroll buttony, podle aktuální potřeby, pro orientaci Vertical
        /// </summary>
        private void ScrollItemsPaintPrepareV()
        {
            Rectangle occupiedArea, actualArea;
            Rectangle area = this.ActualVisibleArea;
            int width = area.Width;
            int height = width / 2;
            int b = this.Panel.ItemBorder;

            if (this.VirtualVisibleArea.Y > this.VirtualOccupiedArea.Y)
            {   // "Nahoru":
                occupiedArea = new Rectangle(area.X, area.Y, width, height);
                actualArea = occupiedArea.Change(b, b, -b, -b);
                this.ScrollItemPrev.SetActualArea(actualArea, occupiedArea, area);
                this.ScrollItemPrev.ImageStandard = IconLibrary.ArrowBlueUp24;
                this.ScrollItemPrev.ImageHotMouse = IconLibrary.ArrowVioletUp24;
            }

            if (this.VirtualVisibleArea.Right < this.VirtualOccupiedArea.Right)
            {   // "Dolů":
                occupiedArea = new Rectangle(area.X, area.Bottom - height, width, height);
                actualArea = occupiedArea.Change(b, b, -b, -b);
                this.ScrollItemPrev.SetActualArea(actualArea, occupiedArea, area);
                this.ScrollItemNext.ImageStandard = IconLibrary.ArrowBlueDown24;
                this.ScrollItemNext.ImageHotMouse = IconLibrary.ArrowVioletDown24;
            }
        }
        /// <summary>
        /// Vykreslí scroll buttony podle potřeby
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="renderData"></param>
        internal void ScrollItemsPaintRender(Graphics graphics, PaintRenderData renderData)
        {
            if (this.ScrollItemPrev.IsVisible)
            {
                this.ScrollItemPrev.PaintRenderActual(graphics, renderData);
            }
            if (this.ScrollItemNext.IsVisible)
            {
                this.ScrollItemNext.PaintRenderActual(graphics, renderData);
            }
        }
        /// <summary>
        /// Scroll button doleva/nahoru (do mínusu)
        /// </summary>
        private ActiveItem ScrollItemPrev;
        /// <summary>
        /// Scroll button doprava/dolů (do plusu)
        /// </summary>
        private ActiveItem ScrollItemNext;
        #endregion
        #region Transformační procesy
        /// <summary>
        /// Zajistí zarovnání bodu _FirstVirtualPoint.
        /// Připraví hodnotu offsetu pro transformace.
        /// </summary>
        private void TransformOffsetPrepare()
        {
            // 1. Pokud by první zobrazený bod (_FirstVirtualPoint) + rozměr aktuálního okna byl větší než virtuální rozměr, pak First bod posunene zpátky k nule:
            int fx = this._FirstVirtualPoint.X;
            int fy = this._FirstVirtualPoint.Y;
            if (fx + this.ActualVisibleArea.Width > this.VirtualOccupiedArea.Right) fx = this.VirtualOccupiedArea.Right - this.ActualVisibleArea.Width;
            if (fy + this.ActualVisibleArea.Height > this.VirtualOccupiedArea.Bottom) fy = this.VirtualOccupiedArea.Bottom - this.ActualVisibleArea.Height;

            // 2. Pokud by modifikovaný bod _FirstVirtualPoint byl pod počátkem virtuálního rozměru, pak First bod nastavíme na počátek virtuální souřadnice:
            if (fx < this.VirtualOccupiedArea.X) fx = this.VirtualOccupiedArea.X;
            if (fy < this.VirtualOccupiedArea.Y) fy = this.VirtualOccupiedArea.Y;
            this._FirstVirtualPoint = new Point(fx, fy);

            // 3. Určíme tedy offset tak, aby následně šlo určit vizuální souřadnici takto: actual = virtual + offset. Tedy: offset = actual - first.virtual:
            this._VirtualToActualOffset = this.ActualVisibleArea.Location.Sub(this._FirstVirtualPoint);

            // 4. Určíme i VirtualVisibleArea = prostor, který je aktuálně viditelný:
            this.VirtualVisibleArea = new Rectangle(this._FirstVirtualPoint, this.ActualVisibleArea.Size);
        }
        /// <summary>
        /// Podle svých dat (surface) vypočítá aktuální (=vizuální) souřadnice pro daný virtuální prvek:
        /// na základě jeho virtuálních souřadnic, a zdejších souřadnic a posuvu.
        /// </summary>
        /// <param name="item"></param>
        internal void CalculateActualArea(VirtualItem item)
        {
            Rectangle actualArea = this.TransformToActual(item.VirtualVisibleArea);
            Rectangle occupiedArea = this.TransformToActual(item.VirtualOccupiedArea);
            Rectangle visibleArea = this.ActualVisibleArea;
            item.SetActualArea(actualArea, occupiedArea, visibleArea);
        }
        /// <summary>
        /// Převede dané souřadnice virtuální na souřadnice aktuální (zobrazované)
        /// </summary>
        /// <param name="virtualArea"></param>
        /// <returns></returns>
        internal Rectangle TransformToActual(Rectangle virtualArea)
        {
            return new Rectangle(virtualArea.Location.Add(this._VirtualToActualOffset), virtualArea.Size);
        }
        private Point _VirtualToActualOffset;
        #endregion
        #region Podpora pro MouseDrag
        public override void MouseDragBegin(Point origin, Point current)
        {
            this._InertialMovement.Stop();
            this.DragBegunAtPoint = current;
            this.DragBeginFirstPixel = this._FirstVirtualPoint;
            this._InertialMovement.ManualDragBegin(this.FirstVirtualPoint);
        }
        public override void MouseDragMove(Point origin, Point current)
        {
            if (this.DragBegunAtPoint.HasValue && this.DragBeginFirstPixel.HasValue)
            {
                Point offset = current.Sub(this.DragBegunAtPoint.Value);
                this.FirstVirtualPoint = this.DragBeginFirstPixel.Value.Sub(offset);
                this._InertialMovement.ManualDragMove(this.FirstVirtualPoint);
                this.Panel.RepaintRequest(true, true, false);
            }
        }
        public override void MouseDragEnd(Point origin, Point target)
        {
            this.DragBegunAtPoint = null;
            this.DragBeginFirstPixel = null;
            this._InertialMovement.ManualDragEnd(this.FirstVirtualPoint);
        }
        private Point? DragBegunAtPoint;
        private Point? DragBeginFirstPixel;
        #endregion
        #region Setrvačný pohyb obsahu
        /// <summary>
        /// Inicializace setrvačného pohybu
        /// </summary>
        private void _InertialMovementInit()
        {
            this._InertialMovement = new InertialMovement(-200d);
            this._InertialMovement.MoveEvent += new InertialMovementEventHandler(_InertialMovement_MoveEvent);
        }
        /// <summary>
        /// Přidání požadavku na pohyb
        /// </summary>
        /// <param name="momentum"></param>
        private void _InertialMovementAdd(double momentum)
        {
            if (momentum == 0f) return;
            double angle = (this.Orientation == System.Windows.Forms.Orientation.Horizontal ? 0d : 90d);
            double acceleration = (this.Orientation == System.Windows.Forms.Orientation.Horizontal ? this.ActualVisibleArea.Width : this.ActualVisibleArea.Height);
            Vector vector = new Vector(angle, momentum * acceleration);
            this._InertialMovement.AddMoveVector(this.FirstVirtualPoint, vector, -acceleration);
        }
        /// <summary>
        /// Handler události, kdy setrvačný pohyb vygeneroval novou pozici
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        void _InertialMovement_MoveEvent(object sender, InertialMovementArgs args)
        {
            Point fp = this.FirstVirtualPoint;
            this.FirstVirtualPoint = args.Target;
            Point np = this.FirstVirtualPoint;
            if (fp == np)
            {   // Souřadnice Před a Po jsou beze změny: zastavíme pohyb, a nebudeme kreslit:
                this._InertialMovement.Stop();
                return;
            }
            if (np != args.Target)
                // Souřadnice Po je jiná než požadovaná = došlo k omezení pohybu, skončíme pohyb (ale kreslit budeme):
                this._InertialMovement.Stop();
            this.Panel.RepaintRequest(true, true, true);
        }
        /// <summary>
        /// Dispose setrvačného pohybu
        /// </summary>
        private void _InertialMovementDispose()
        {
            if (this._InertialMovement != null)
                ((IDisposable)this._InertialMovement).Dispose();
            this._InertialMovement = null;
        }
        /// <summary>
        /// Objekt pro řízení pohybu
        /// </summary>
        private InertialMovement _InertialMovement;
        #endregion
        #region Public podpora pro pohyby (detekce pohybu, zastavení, rozběhnutí)
        /// <summary>
        /// true, pokud právě probíhá setrvačný pohyb. False = neprobíhá.
        /// </summary>
        public bool IsCurrentlyMoving { get { return this._InertialMovement.IsCurrentlyMoving; } }
        /// <summary>
        /// Zastaví setrvačný pohyb, okamžitě. Ihned poté je IsCurrentlyMoving = false, a nepřijde žádná událost MoveEvent.
        /// Objekt je nadále běžně použitelný, stačí nastartovat další pohyb odpovídající metodou.
        /// </summary>
        public void Stop() { this._InertialMovement.Stop(); }
        /// <summary>
        /// Pokud uživatel stiskl myš v prostoru tohoto povrchu, a pokud se povrch pohybuje, 
        /// a pokud to bylo v prostoru prvku (a ne na jeho Scroll buttonech), 
        /// pak se v této metodě provede zastavení pohybu a vrátí se true, 
        /// což volající vyhodnotí jako požadavek na ignorování události MouseDown 
        /// (prostě: běžící povrch se stiskem myši zastaví, ale neprovede se kliknutí na item, který byl náhodou pod myší).
        /// </summary>
        /// <returns></returns>
        internal bool IsMouseDownStopMoving(Point location)
        {
            if (!this.IsCurrentlyMoving) return false;
            if (!this.ActualVisibleArea.Contains(location)) return false;
            if (this.ScrollItemPrev.IsVisible && this.ScrollItemPrev.ActualVisibleArea.Contains(location)) return false;
            if (this.ScrollItemNext.IsVisible && this.ScrollItemNext.ActualVisibleArea.Contains(location)) return false;
            this.Stop();
            return true;
        }
        #endregion
    }
    #endregion
    #region VirtualItem : podklad tříd, které mohou být interaktivní a mohou mít virtuální a reálné souřadnice
    /// <summary>
    /// VirtualItem : podklad tříd, které mohou být interaktivní a mohou mít virtuální a reálné souřadnice
    /// </summary>
    public class VirtualItem : ActiveItem
    {
        #region Virtuální souřadnice
        /// <summary>
        /// Uloží dodané souřadnice jako VirtualVisibleArea a VirtualOccupiedArea.
        /// </summary>
        /// <param name="visibleArea">Virtuální souřadnice k zobrazení</param>
        /// <param name="occupiedArea">Virtuální souřadnice včetně okrajů (okraje nejsou aktivní)</param>
        internal virtual void SetVirtualArea(Rectangle visibleArea, Rectangle occupiedArea)
        {
            this.VirtualVisibleArea = visibleArea;
            this.VirtualOccupiedArea = occupiedArea;
        }
        /// <summary>
        /// Souřadnice tohoto prvku ve virtuálních koordinátech (počátkem systému je bod 0/0 prvního prvku), které prvek zabírá včetně neaktivních okrajů (kde prosvítá podklad)
        /// </summary>
        public Rectangle VirtualOccupiedArea { get; protected set; }
        /// <summary>
        /// Souřadnice tohoto prvku ve virtuálních koordinátech (počátkem systému je bod 0/0 prvního prvku), kde je prvek viditelný a aktivní
        /// </summary>
        public Rectangle VirtualVisibleArea { get; protected set; }
        #endregion
    }
    #endregion
    #region ActiveItem : podklad tříd, které mohou být interaktivní a mají tedy vizuální souřadnice
    /// <summary>
    /// ActiveItem : podklad tříd, které mohou být interaktivní
    /// </summary>
    public class ActiveItem
    {
        #region Souřadnice a viditelnost prvku, stav a tooltip a tvar
        public ActiveItem()
        {
            this.State = PaintState.Standard;
            this.BorderColorActive = Color.FromArgb(196, Color.LightYellow);
        }
        /// <summary>
        /// Stav prvku
        /// </summary>
        public virtual PaintState State { get; set; }
        /// <summary>
        /// Prvek je aktivní? (Status je (Focused || HotMouse || Pressed))
        /// </summary>
        public virtual bool IsActive { get { return ((this.State & (PaintState.Focused | PaintState.HotMouse | PaintState.Pressed)) != 0); } }
        /// <summary>
        /// Skryje this prvek. Nemění jeho souřadnice, jen nastaví IsVisible = false;
        /// </summary>
        public void Hide()
        {
            this.IsVisible = false;
        }
        /// <summary>
        /// true pokud tento prvek je alespoň zčásti viditelný
        /// </summary>
        public bool IsVisible { get; private set; }
        /// <summary>
        /// Nastaví aktuálně viditelné souřadnice: ActualTotalArea = (actualArea), ActualVisibleArea = Clip(actualArea, visibleArea), ActualOccupiedArea = Clip(actualArea, visibleArea).
        /// Nastaví IsVisible podle výsledku v this.ActualVisibleArea.
        /// </summary>
        /// <param name="actualArea">Vizuální souřadnice vnitřní, teoretické (neoříznuté)</param>
        /// <param name="visibleArea">Prostor pro vykreslení</param>
        internal void SetActualArea(Rectangle actualArea, Rectangle visibleArea)
        {
            this.SetActualArea(actualArea, actualArea, visibleArea);
        }
        /// <summary>
        /// Nastaví aktuálně viditelné souřadnice: ActualTotalArea = (actualArea), ActualVisibleArea = Clip(actualArea, visibleArea), ActualOccupiedArea = Clip(actualArea+border, visibleArea).
        /// Nastaví IsVisible podle výsledku v this.ActualVisibleArea.
        /// </summary>
        /// <param name="actualArea">Vizuální souřadnice vnitřní, teoretické (neoříznuté)</param>
        /// <param name="border">Okraj pro určení souřadnic ActualOccupiedArea</param>
        /// <param name="visibleArea">Prostor pro vykreslení</param>
        internal void SetActualArea(Rectangle actualArea, int border, Rectangle visibleArea)
        {
            Rectangle occupiedArea = actualArea;
            if (border > 0)
                occupiedArea.Inflate(border, border);
            this.SetActualArea(actualArea, occupiedArea, visibleArea);
        }
        /// <summary>
        /// Nastaví aktuálně viditelné souřadnice: ActualTotalArea = (actualArea), ActualVisibleArea = Clip(actualArea, visibleArea), ActualOccupiedArea = Clip(actualArea+border, visibleArea).
        /// Nastaví IsVisible podle výsledku v this.ActualVisibleArea.
        /// </summary>
        /// <param name="actualArea">Vizuální souřadnice vnitřní, teoretické (neoříznuté)</param>
        /// <param name="visibleArea">Prostor pro vykreslení</param>
        /// <param name="occupiedArea">Okraj pro určení souřadnic ActualOccupiedArea</param>
        internal virtual void SetActualArea(Rectangle actualArea, Rectangle occupiedArea, Rectangle visibleArea)
        {
            this.ActualTotalArea = actualArea;
            this.ActualVisibleArea = Rectangle.Intersect(actualArea, visibleArea);
            this.ActualOccupiedArea = Rectangle.Intersect(occupiedArea, visibleArea);
            this.IsVisible = (this.ActualVisibleArea.Width > 0 && this.ActualVisibleArea.Height > 0);
        }
        /// <summary>
        /// Souřadnice tohoto prvku ve vizuálních koordinátech (počátkem systému je fyzický bod 0/0 controlu CommandPanel),
        /// které prvek zabírá včetně neaktivních okrajů (kde prosvítá podklad).
        /// Tyto souřadnice JSOU oříznuté do viditelného prostoru.
        /// </summary>
        public Rectangle ActualOccupiedArea { get; protected set; }
        /// <summary>
        /// Souřadnice tohoto prvku ve vizuálních koordinátech (počátkem systému je fyzický bod 0/0 controlu CommandPanel), kde je prvek umístěn.
        /// Tyto souřadnice NEJSOU oříznuté do viditelného prostoru, podle těchto souřadnic se generují grafické nástroje pro kreslení.
        /// </summary>
        public Rectangle ActualTotalArea { get; protected set; }
        /// <summary>
        /// Souřadnice tohoto prvku ve vizuálních koordinátech (počátkem systému je fyzický bod 0/0 controlu CommandPanel), kde je prvek viditelný a aktivní.
        /// Tyto souřadnice JSOU oříznuté do viditelného prostoru, zde je control fyzicky kreslen a aktivní.
        /// </summary>
        public Rectangle ActualVisibleArea { get; protected set; }
        /// <summary>
        /// Titulek tooltipu
        /// </summary>
        public virtual string ToolTipTitle { get; set; }
        /// <summary>
        /// Text tooltipu
        /// </summary>
        public virtual string ToolTipText { get; set; }
        #endregion
        #region Interaktivita
        public virtual void MouseDragBegin(Point origin, Point current) { this.OnMouseDragBegin(origin, current); }
        public virtual void MouseDragMove(Point origin, Point current) { this.OnMouseDragMove(origin, current); }
        public virtual void MouseDragEnd(Point origin, Point current) { this.OnMouseDragEnd(origin, current); }
        public virtual void MouseLeftClick() { this.OnMouseLeftClick(); }
        public virtual void MouseRightClick() { this.OnMouseRightClick(); }
        public event ActiveItemDragHandler MouseDragBeginEvent;
        public event ActiveItemDragHandler MouseDragMoveEvent;
        public event ActiveItemDragHandler MouseDragEndEvent;
        public event EventHandler MouseLeftClickEvent;
        public event EventHandler MouseRightClickEvent;
        protected virtual void OnMouseDragBegin(Point origin, Point current){ if (this.MouseDragBeginEvent != null) this.MouseDragBeginEvent(this, new ActiveItemDragArgs(origin, current)); }
        protected virtual void OnMouseDragMove(Point origin, Point current) { if (this.MouseDragMoveEvent != null) this.MouseDragMoveEvent(this, new ActiveItemDragArgs(origin, current)); }
        protected virtual void OnMouseDragEnd(Point origin, Point current) { if (this.MouseDragEndEvent != null) this.MouseDragEndEvent(this, new ActiveItemDragArgs(origin, current)); }
        protected virtual void OnMouseLeftClick() { if (this.MouseLeftClickEvent != null) this.MouseLeftClickEvent(this, new EventArgs()); }
        protected virtual void OnMouseRightClick() { if (this.MouseRightClickEvent != null) this.MouseRightClickEvent(this, new EventArgs()); }
        #endregion
        #region PaintRender
        /// <summary>
        /// Pokud this je IsVisible: vykreslí this button do prostoru this.ActualVisibleArea, vloží se do renderData.ActiveItemList.
        /// Tato metoda tedy už nepočítá svoji pozici a neurčuje IsVisible.
        /// </summary>
        /// <param name="graphics"></param>
        /// <param name="renderData"></param>
        internal virtual void PaintRenderActual(Graphics graphics, PaintRenderData renderData)
        {
            if (this.IsVisible)
            {
                renderData.ActiveItemList.Add(this);
                this.PaintActual(graphics);
            }
        }
        /// <summary>
        /// Fyzické kreslení this do grafiky. Kreslí se do souřadnic ActualVisibleArea.
        /// </summary>
        /// <param name="graphics"></param>
        internal virtual void PaintActual(Graphics graphics)
        {
            // Background:
            Color? backColor = this.BackColor;
            if (backColor.HasValue)
            {
                System.Drawing.Drawing2D.GraphicsPath shape = this.GetShape();
                Painter.PaintBackground(this.ActualTotalArea, shape, graphics, 0, BackColor.Value, this.State);
            }

            // Image:
            Image image = this.Image;
            if (image != null)
                Painter.PaintImage(graphics, image, this.ActualTotalArea, ContentAlignment.MiddleCenter);

            // Border:
            Color? borderColor = this.BorderColor;
            if (borderColor.HasValue)
            {
                System.Drawing.Drawing2D.GraphicsPath shape = this.GetShape();
                if (shape != null)
                {
                    using (Pen pen = new Pen(borderColor.Value))
                    {
                        graphics.DrawPath(pen, shape);
                    }
                }
                else
                {
                    Rectangle area = this.ActualVisibleArea;
                    area.Width--;
                    area.Height--;
                    using (Pen pen = new Pen(borderColor.Value))
                    {
                        graphics.DrawRectangle(pen, area);
                    }
                }
            }
        }
        protected virtual System.Drawing.Drawing2D.GraphicsPath GetShape()
        {
            return Asol.Reporting.Editor.Components.ShapeGenerator.GetRoundRectangle(this.ActualTotalArea, 3f, 3f);
        }
        #endregion
        #region BackColor
        /// <summary>
        /// Barva prvku aktuální (daná stavem prvku a výběrem barev this.BackColor***)
        /// </summary>
        public virtual Color? BackColor
        {
            get
            {
                PaintState state = this.State;
                if (state == PaintState.None) return null;
                bool isDisabled = ((state & PaintState.Disabled) != 0);
                bool isStandard = ((state & PaintState.Standard) != 0);
                bool isHotMouse = ((state & PaintState.HotMouse) != 0);
                bool isFocused = ((state & PaintState.Focused) != 0);
                bool isPressed = ((state & PaintState.Pressed) != 0);

                if (isDisabled) return Assistant.FirstNotNull(this.BackColorDisabled, this.BackColorStandard);
                else if (isPressed) return Assistant.FirstNotNull(this.BackColorPressed, this.BackColorHotMouse, this.BackColorFocused, this.BackColorStandard);
                else if (isHotMouse && isFocused) return Assistant.FirstNotNull(this.BackColorHotMouse, this.BackColorFocused, this.BackColorStandard);
                else if (isFocused) return Assistant.FirstNotNull(this.BackColorFocused, this.BackColorHotMouse, this.BackColorStandard);
                else if (isHotMouse) return Assistant.FirstNotNull(this.BackColorHotMouse, this.BackColorFocused, this.BackColorStandard);
                else return this.BackColorStandard;
            }
        }
        /// <summary>
        /// Barva prvku ve stavu Standard
        /// </summary>
        public virtual Color? BackColorStandard { get; set; }
        /// <summary>
        /// Barva prvku ve stavu Disabled
        /// </summary>
        public virtual Color? BackColorDisabled { get; set; }
        /// <summary>
        /// Barva prvku ve stavu HotMouse
        /// </summary>
        public virtual Color? BackColorHotMouse { get; set; }
        /// <summary>
        /// Barva prvku ve stavu Focused
        /// </summary>
        public virtual Color? BackColorFocused { get; set; }
        /// <summary>
        /// Barva prvku ve stavu Pressed
        /// </summary>
        public virtual Color? BackColorPressed { get; set; }
        /// <summary>
        /// Barva rámečku v aktuálním stavu
        /// </summary>
        public virtual Color? BorderColor { get { return (this.IsActive ? this.BorderColorActive : this.BorderColorNonActive); } }
        /// <summary>
        /// Barva rámečku ve stavu NonActive
        /// </summary>
        public virtual Color? BorderColorNonActive { get; set; }
        /// <summary>
        /// Barva rámečku ve stavu Active
        /// </summary>
        public virtual Color? BorderColorActive { get; set; }
        #endregion
        #region Image
        /// <summary>
        /// Obrázek aktuální (daný stavem prvku a výběrem obrázků this.Image***)
        /// </summary>
        public virtual Image Image
        {
            get
            {
                PaintState state = this.State;
                if (state == PaintState.None) return null;
                bool isDisabled = ((state & PaintState.Disabled) != 0);
                bool isStandard = ((state & PaintState.Standard) != 0);
                bool isHotMouse = ((state & PaintState.HotMouse) != 0);
                bool isFocused = ((state & PaintState.Focused) != 0);
                bool isPressed = ((state & PaintState.Pressed) != 0);

                if (isDisabled) return Assistant.FirstNotNull(this.ImageDisabled, this.ImageStandard);
                else if (isPressed) return Assistant.FirstNotNull(this.ImagePressed, this.ImageHotMouse, this.ImageFocused, this.ImageStandard);
                else if (isHotMouse && isFocused) return Assistant.FirstNotNull(this.ImageHotMouse, this.ImageFocused, this.ImageStandard);
                else if (isFocused) return Assistant.FirstNotNull(this.ImageFocused, this.ImageHotMouse, this.ImageStandard);
                else if (isHotMouse) return Assistant.FirstNotNull(this.ImageHotMouse, this.ImageFocused, this.ImageStandard);
                else return this.ImageStandard;
            }
        }
        /// <summary>
        /// Barva prvku ve stavu Standard
        /// </summary>
        public virtual Image ImageStandard { get; set; }
        /// <summary>
        /// Barva prvku ve stavu Disabled
        /// </summary>
        public virtual Image ImageDisabled { get; set; }
        /// <summary>
        /// Barva prvku ve stavu HotMouse
        /// </summary>
        public virtual Image ImageHotMouse { get; set; }
        /// <summary>
        /// Barva prvku ve stavu Focused
        /// </summary>
        public virtual Image ImageFocused { get; set; }
        /// <summary>
        /// Barva prvku ve stavu Pressed
        /// </summary>
        public virtual Image ImagePressed { get; set; }
        #endregion
    }
    public delegate void ActiveItemDragHandler( object sender, ActiveItemDragArgs args);
    public class ActiveItemDragArgs : EventArgs
    {
        public ActiveItemDragArgs(Point origin, Point current)
        {
            this.Origin = origin;
            this.Current = current;
        }
        public Point Origin { get; private set; }
        public Point Current { get; private set; }
    }
    #endregion
    #region PaintPrepareData, PaintRenderData, CommandArgs : třídy argumentů pro spolupráci mezi daty jednotlivých tříd Command*
    /// <summary>
    /// Data pro přípravu souřadnic před kreslením
    /// </summary>
    internal class PaintPrepareData : CommandArgs
    {
        internal PaintPrepareData(CommandPanel panel)
            : base(panel)
        { }
        /// <summary>
        /// Virtuální prostor aktuální grupy, jejího záhlaví. Odpovídá hodnotě VirtualOccupiedArea.
        /// </summary>
        internal Rectangle CurrentGroupVirtualArea { get; private set; }
        /// <summary>
        /// Virtuální prostor aktuální skupiny položek
        /// </summary>
        internal Rectangle CurrentItemVirtualArea { get; private set; }
        /// <summary>
        /// Připraví se na příjem dat nových požadavků na prostor
        /// </summary>
        internal void PrepareNewArea(int groupSpace, int itemSpace)
        {
            this.CurrentGroupVirtualArea = new Rectangle(this.GroupSurface.GetVirtualNextPoint(groupSpace), Size.Empty);
            this.CurrentItemVirtualArea = new Rectangle(this.ItemSurface.GetVirtualNextPoint(itemSpace), Size.Empty);
        }
        /// <summary>
        /// Převezme danou velikost (viditelnou = aktivní), zvětší ji o border, a umístí ji do prostoru záhlaví skupin (GroupSurface).
        /// Tím určí její souřadnice viditelné i obsazené (visibleArea i occupiedArea), které dá na výstup.
        /// Současně o souřadnice occupiedArea zvětší prostor CurrentGroupVirtualArea.
        /// </summary>
        /// <param name="visibleSize">Velikost viditelného prvku</param>
        /// <param name="border">Okraje nad rámec viditelného prvku (jsou součástí OccupiedArea)</param>
        /// <param name="visibleArea">Výstup viditelné souřadnice</param>
        /// <param name="occupiedArea">Výstup occupied souřadnice</param>
        internal void AddGroupArea(Size visibleSize, int border, out Rectangle visibleArea, out Rectangle occupiedArea)
        {
            this.CurrentGroupVirtualArea = GetEnlargedArea(this.CurrentGroupVirtualArea, this.GroupSurface.Orientation, visibleSize, border, out visibleArea, out occupiedArea);
            this.GroupSurface.AddVirtualArea(occupiedArea);
        }
        /// <summary>
        /// Převezme danou velikost (viditelnou = aktivní), zvětší ji o border, a umístí ji do prostoru záhlaví skupin (GroupSurface).
        /// Tím určí její souřadnice viditelné i obsazené (visibleArea i occupiedArea), které dá na výstup.
        /// Současně o souřadnice occupiedArea zvětší prostor CurrentGroupVirtualArea.
        /// </summary>
        /// <param name="visibleSize">Velikost viditelného prvku</param>
        /// <param name="border">Okraje nad rámec viditelného prvku (jsou součástí OccupiedArea)</param>
        /// <param name="visibleArea">Výstup viditelné souřadnice</param>
        /// <param name="occupiedArea">Výstup occupied souřadnice</param>
        internal void AddItemArea(Size visibleSize, int border, out Rectangle visibleArea, out Rectangle occupiedArea)
        {
            this.CurrentItemVirtualArea = GetEnlargedArea(this.CurrentItemVirtualArea, this.ItemSurface.Orientation, visibleSize, border, out visibleArea, out occupiedArea);
            this.ItemSurface.AddVirtualArea(occupiedArea);
        }
        /// <summary>
        /// Určí souřadnice visibleArea a occupiedArea na výchozí souřadnici dané prostorem (area), jejím NextPointenm v dané orientaci.
        /// Vrací vstupní souřadnice (area) rozšířené o occupiedArea.
        /// </summary>
        /// <param name="area"></param>
        /// <param name="visibleSize"></param>
        /// <param name="border"></param>
        /// <param name="visibleArea"></param>
        /// <param name="occupiedArea"></param>
        /// <returns></returns>
        protected static Rectangle GetEnlargedArea(Rectangle area, Orientation orientation, Size visibleSize, int border, out Rectangle visibleArea, out Rectangle occupiedArea)
        {
            Point nextOccupiedPoint = area.NextPoint(orientation, 0);
            Size occupiedSize = new Size(visibleSize.Width + 2 * border, visibleSize.Height + 2 * border);
            occupiedArea = new Rectangle(nextOccupiedPoint, occupiedSize);
            Point nextVisiblePoint = new Point(nextOccupiedPoint.X + border, nextOccupiedPoint.Y + border);
            visibleArea = new Rectangle(nextVisiblePoint, visibleSize);
            return area.Enlarge(occupiedArea);
        }
    }
    /// <summary>
    /// Data pro provedení kreslení
    /// </summary>
    internal class PaintRenderData : CommandArgs
    {
        internal PaintRenderData(CommandPanel panel)
            : base(panel)
        {
            this.ActiveItemList = new List<ActiveItem>();
        }
        /// <summary>
        /// Seznam command itemů aktivních v aktuální ploše
        /// </summary>
        internal List<ActiveItem> ActiveItemList { get; private set; }
    }
    /// <summary>
    /// Bázová třída pro argumenty uvnitř CommandPanelu
    /// </summary>
    internal class CommandArgs
    {
        internal CommandArgs(CommandPanel panel)
        {
            this.CommandPanel = panel;
        }
        /// <summary>
        /// Panel
        /// </summary>
        internal CommandPanel CommandPanel { get; private set; }
        /// <summary>
        /// Orientace panelu
        /// </summary>
        internal Orientation Orientation { get { return this.CommandPanel.Orientation; } }
        /// <summary>
        /// Controler prostoru Group (záhlaví)
        /// </summary>
        internal CommandSurface GroupSurface { get { return this.CommandPanel.GroupSurface; } }
        /// <summary>
        /// Controler prostoru Itemů (tlačítka)
        /// </summary>
        internal CommandSurface ItemSurface { get { return this.CommandPanel.ItemSurface; } }
    }
    #endregion

    #region Setrvačný pohyb řízený vláknem na pozadí
    /// <summary>
    /// InertialMovement : Setrvačný pohyb řízený vláknem na pozadí
    /// </summary>
    public class InertialMovement : IDisposable
    {
        #region Konstrukce a BackThread loop
        public InertialMovement()
            : this(-50d)
        { }
        public InertialMovement(double? acceleration)
        {
            this.Acceleration = acceleration;
            this._BackThreadStop = false;
            this._BackThread = new System.Threading.Thread(this._BackThreadLoop);
            this._BackThread.Name = "InertialThread";
            this._BackThread.IsBackground = true;
            this._BackThread.Priority = System.Threading.ThreadPriority.BelowNormal;
            this._BackThread.Start();
        }
        /// <summary>
        /// Hlavní smyčka threadu na pozadí
        /// </summary>
        private void _BackThreadLoop()
        {
            this._BackAlarm = new System.Threading.AutoResetEvent(false);
            while (!this._BackThreadStop)
            {
                if (this._BackThreadStop) break;

                // Pokud má být proveden pohyb, provedu ho a získám informaci o čase, po který mohu spát:
                TimeSpan? wait = this.DoInertialMove();
                if (wait.HasValue)
                    this._BackAlarm.WaitOne(wait.Value);
                else
                    this._BackAlarm.WaitOne();
            }
        }
        /// <summary>
        /// Dispose
        /// </summary>
        void IDisposable.Dispose()
        {
            this._BackThreadStop = true;
            this._BackAlarm.Set();
        }
        private System.Threading.AutoResetEvent _BackAlarm;
        private System.Threading.Thread _BackThread;
        private bool _BackThreadStop;

        private static string LogFile = Assistant.GetFileNameOnPath(Assistant.PathType.ExecutablePath, "InertialLog.txt");
        internal static void AddLogLine(string text)
        {
            try
            {
                System.IO.File.AppendAllText(LogFile, text + Environment.NewLine);
            }
            catch { }
        }
        #endregion
        #region Public members
        /// <summary>
        /// Koeficient setrvačnosti plochy. Má význam počtu sekund, po kterých se zastaví etalonový pohyb rychlosti 100px/sec.
        /// 0=žádný setrvačný pohyb nebude.
        /// +nekonečno = pohyb nebrzdí, dojede výchozí = konstantní rychlostí až na konec.
        /// Záporné hodnoty mají význam jako 0.
        /// Implicitní hodnota = 1.
        /// </summary>
        public double? Acceleration { get; set; }
        /// <summary>
        /// Událost, kterou vyvolá třída InertialMovement když má dojít k pohybu.
        /// V argumentu je předán požadovaný bod cíle.
        /// </summary>
        public event InertialMovementEventHandler MoveEvent;
        /// <summary>
        /// Vyvolá event MoveEvent
        /// </summary>
        /// <param name="target"></param>
        protected virtual void OnMoveEvent(Point target)
        {
            if (this.MoveEvent != null)
            {
                InertialMovementArgs args = new InertialMovementArgs(target);
                this.MoveEvent(this, args);
                if (args.Cancel)
                    this.Stop();
            }
        }
        /// <summary>
        /// Událost, kterou vyvolá třída InertialMovement když má dojít k pohybu.
        /// V argumentu je předán požadovaný bod cíle.
        /// </summary>
        public event EventHandler StopEvent;
        /// <summary>
        /// Vyvolá event StopEvent
        /// </summary>
        /// <param name="target"></param>
        protected virtual void OnStopEvent()
        {
            if (this.StopEvent != null)
                this.StopEvent(this, new EventArgs());
        }
        /// <summary>
        /// Zajistí, že surface se bude pohybovat směrem definovaným ve vektoru.
        /// </summary>
        /// <param name="currentPoint">Aktuální bod, který se bude pohybovat (jeho upravené souřadnice budou předávány do eventu Move)</param>
        /// <param name="vector">Vektor rychlosti (pixel/sec) a směr pohybu</param>
        public void AddMoveVector(PointF currentPoint, Vector vector)
        {
            this.AddMoveVector(currentPoint, vector, this.Acceleration);
        }
        /// <summary>
        /// Zajistí, že surface se bude pohybovat směrem definovaným ve vektoru.
        /// </summary>
        /// <param name="currentPoint">Aktuální bod, který se bude pohybovat (jeho upravené souřadnice budou předávány do eventu Move)</param>
        /// <param name="vector">Vektor rychlosti (pixel/sec) a směr pohybu</param>
        public void AddMoveVector(PointF currentPoint, Vector vector, double? acceleration)
        {
            AddLogLine("---------------------------------------------------------------------------------------");
            InertialMovementData moveInfo = this._MovementData;
            if (moveInfo == null)
            {
                moveInfo = new InertialMovementData(currentPoint, vector, acceleration);
                AddLogLine("AddMoveVector: CurrentPoint=" + currentPoint.ToString() + "; Vector=" + vector.ToString() + "; new vector.");
            }
            else
            {
                moveInfo.AddMoveVector(vector);
                AddLogLine("AddMoveVector: CurrentPoint=" + currentPoint.ToString() + "; Vector=" + vector.ToString() + "; AddMoveVector.");
            }
            this._MovementData = moveInfo;
            this._BackAlarm.Set();
        }
        /// <summary>
        /// Nastartuje daný pohyb bez ohledu na současný stav pohybu.
        /// </summary>
        /// <param name="currentPoint"></param>
        /// <param name="vector"></param>
        /// <param name="acceleration"></param>
        public void SetMoveVector(PointF currentPoint, Vector vector, double? acceleration)
        {
            AddLogLine("---------------------------------------------------------------------------------------");
            InertialMovementData moveInfo = new InertialMovementData(currentPoint, vector, acceleration);
            AddLogLine("SetMoveVector: CurrentPoint=" + currentPoint.ToString() + "; Vector=" + vector.ToString() + ".");
            this._MovementData = moveInfo;
            this._BackAlarm.Set();
        }
        /// <summary>
        /// true, pokud právě probíhá setrvačný pohyb. False = neprobíhá.
        /// </summary>
        public bool IsCurrentlyMoving { get { return (this._MovementData != null); } }
        /// <summary>
        /// Zastaví setrvačný pohyb, okamžitě. Ihned poté je IsCurrentlyMoving = false, a nepřijde žádná událost MoveEvent.
        /// Objekt je nadále běžně použitelný, stačí nastartovat další pohyb odpovídající metodou.
        /// </summary>
        public void Stop()
        {
            AddLogLine("Stop().");
            this._MovementData = null;
        }
        #endregion
        #region Setrvačný pohyb daný rychlostí a zápornou akcelerací
        /// <summary>
        /// Metoda volaná tehdy, když máme provést další krok pohybu. Může být volaná i když není zapotřebí, pak vrátí null.
        /// Provede pohyb, a vrátí časový úsek, po kterém má být znovu vyvolána. Může vrátit null = pro tentokrát už není třeba, aktuální pohyb skončil.
        /// Metoda se volá v threadu na pozadí. Metoda může vyvolat překreslení panelu.
        /// </summary>
        /// <returns></returns>
        private TimeSpan? DoInertialMove()
        {
            InertialMovementData moveInfo = this._MovementData;
            if (moveInfo == null)
            {
                AddLogLine("DoInertialMove: skip, does not have MoveInfo.");
                return null;
            }

            DateTime start = DateTime.Now;
            DateTime lastTime = moveInfo.TimeBefore;
            TimeSpan? callMeAfterTime;
            Point? targetN = moveInfo.GetNextPoint(out callMeAfterTime);
            if (targetN.HasValue)
            {
                Point target = targetN.Value;
                if (!this._MoveLastPoint.HasValue || (this._MoveLastPoint.HasValue && (this._MoveLastPoint.Value != target)))
                {
                    AddLogLine("DoInertialMove: Time:" + start.ToString("mm.ss.fff") + "; Move to TargetPoint=" + targetN.ToString() + "; call event.");
                    this.OnMoveEvent(target);
                    this._MoveLastPoint = target;
                }
                else
                {
                    AddLogLine("DoInertialMove: Time:" + start.ToString("mm.ss.fff") + "; Move to TargetPoint=" + targetN.ToString() + ".");
                }
            }

            if (callMeAfterTime.HasValue)
            {
                AddLogLine("DoInertialMove: Call me next after " + callMeAfterTime.Value.TotalMilliseconds.ToString("# ##0.0") + "milisec.");
                TimeSpan callAfter = start.Add(callMeAfterTime.Value) - DateTime.Now;
                if (callAfter.TotalMilliseconds < 25d)
                    callAfter = TimeSpan.FromMilliseconds(25d);
                return callAfter;
            }
            else
            {
                AddLogLine("DoInertialMove: Done, dont call me.");
                this._MovementData = null;
                return null;
            }
        }
        private Point? _MoveLastPoint;
        private InertialMovementData _MovementData;
        private class InertialMovementData
        {
            public InertialMovementData(PointF point, Vector moveVector, double? acceleration)
            {
                this.Point = new PointD(point);
                this.Vector = moveVector;
                this.Acceleration = (acceleration.HasValue && acceleration.Value > 0f ? 0f : acceleration);
                this.TimeBefore = DateTime.Now;
            }
            public void AddMoveVector(Vector moveVector)
            {
                this.Vector = this.Vector + moveVector;
            }
            /// <summary>
            /// Souřadnice bodu při předešlém kroku
            /// </summary>
            public PointD Point { get; private set; }
            /// <summary>
            /// Rychlost pohybu na začátku následujícího kroku
            /// </summary>
            public Vector Vector { get; private set; }
            /// <summary>
            /// Čas předešlého kroku
            /// </summary>
            public DateTime TimeBefore { get; private set; }
            /// <summary>
            /// Akcelerace pohybu. Zadává se záporné číslo, které vyjadřuje "zpomalení" rychlosti pohybu v pixelech za sekundu na druhou (změna rychlosti po jedné sekundě).
            /// Pokud je rovno 0, pak se pohyb nezpomaluje, zastaví až na konci plochy. Kladné hodnoty (které by reprezentovaly nekonečné zrychlování) se chápu jako 0.
            /// Pokud je třeba zajistit nesetrvačný pohyb (kdy se souřadnice změní jen v okamžiku požadavku a pak se zastaví), pak Acceleration = null.
            /// </summary>
            public double? Acceleration { get; private set; }
            /// <summary>
            /// true pokud this je prázdný = vektor pohybu je na nule, už nebude žádný pohyb.
            /// </summary>
            public bool IsEmpty { get { return this.Vector.IsEmpty; } }
            /// <summary>
            /// Určí souřadnice dalšího bodu, při bodu výchozím a aktuálním vektoru + uplynulém čase a koeficientu.
            /// Pokud další bod nebude, vrátí null.
            /// </summary>
            /// <returns></returns>
            internal Point? GetNextPoint(out TimeSpan? callMeAfterTime)
            {
                callMeAfterTime = null;
                if (this.IsEmpty) return null;

                bool stopped = false;
                Vector vector1 = this.Vector;
                Vector vector2 = Vector.Empty;
                Vector track = Vector.Empty;
                DateTime now = DateTime.Now;
                if (!this.Acceleration.HasValue)
                {   //  Ne-setrvačný pohyb:
                    vector2 = Vector.Empty;
                    if (!vector1.IsEmpty)
                        track = vector1 * 10d;
                    stopped = true;
                    InertialMovement.AddLogLine("GetNextPoint: Stopped (no inertial move).");
                }
                else
                {   //  Setrvačný pohyb:
                    // Vychází se z vektoru pohybu, z jeho délky (vector.Length), který reprezentuje rychlost v pixelech za sekundu.
                    // Rychlost se postupně snižuje o hodnotu akcelereace (která má rozměr v pixelech za sekundu na druhou, klasický fyzikální údaj "zrychlení", a má mít zápornou hodnotu).
                    // Určíme koncovou rychlost po uplynulém čase, a pokud by byla záporná pak určíme čas, kdy došla do nuly.
                    // Určíme dráhu dosaženou za daný čas a danou průměrnou rychlostí.
                    // Určíme cílový bod jako bod výchozí plus vektor (daný úhlem rychlosti a dráhou).
                    // Cílový upravený bod a sníženou rychlost uložíme.
                    double time = ((TimeSpan)(now - this.TimeBefore)).TotalSeconds;
                    double accel = this.Acceleration.Value;    // Zrychlení, v pix/sec^2
                    if (accel > 0d) accel = 0d;                // Nesmí být kladné
                    double v1 = vector1.Length;                // Rychlost na počátku intervalu, pix/sec
                    double v2 = v1;
                    if (v1 > 0d)                               // Nemáme rychlost => nelze určit další bod (= zastavení systému)
                    {
                        v2 = v1 + (accel * time);              // Rychlost na konci intervalu, pix/sec, může být záporná (pokud výchozí rychlost byla nižší, než kolik odpovídá změně rychlosti za daný čas
                        if (v2 < 0d)
                        {   // Dojeli jsme. Určíme kdy to bylo (jaký čas jsme jeli):
                            time = v1 / (-accel);
                            v2 = 0d;
                            stopped = true;
                            InertialMovement.AddLogLine("GetNextPoint: Stopped (v2 == 0).");
                        }
                        track = new Vector(vector1.Angle, ((v1 + v2) / 2d * time));   // Dráha, kterou jsme ve (směru vektoru rychlosti) ujeli průměrnou rychlostí ((v1 + v2) / 2d) za čas, kdy jsme jeli (time)
                    }
                    else
                    {
                        stopped = true;
                        InertialMovement.AddLogLine("GetNextPoint: Stopped (v1 == 0).");
                    }
                    vector2 = new Vector(vector1.Angle, v2);
                    InertialMovement.AddLogLine("GetNextPoint: v1=" + v1.ToString("###0.000") + "px/s; time=" + time.ToString("###0.000") + "sec; v2=" + v2.ToString("###0.000") + "px/s; track=" + track.ToString());
                }

                this.Vector = vector2;
                this.TimeBefore = now;
                this.Point = this.Point + track;

                Point? result = (Point)(this.Point);          // Explicitní konverze provádí Round souřadnic Decimal na Int
                if (!stopped)
                    callMeAfterTime = TimeSpan.FromMilliseconds(25) - (TimeSpan)(DateTime.Now - now);
                return result;
            }
        }
        #endregion
        #region Setrvačný pohyb odvozený od ručního tažení povrchu, s následným dojezdem
        /// <summary>
        /// Zahájení ručního pohybu na daném bodě
        /// </summary>
        /// <param name="point"></param>
        internal void ManualDragBegin(Point point)
        {
            this.ManualDragQueue = new Queue<Tuple<DateTime, Point>>();
        }
        /// <summary>
        /// Záznam pohybu na daný bod v aktuálním čase
        /// </summary>
        /// <param name="point"></param>
        internal void ManualDragMove(Point point)
        {
            DateTime now = DateTime.Now;
            // Odebereme starší záznamy, které se týkají doby před 1.5 sekundou a starší, na ně už nebudeme nikdy brát ohled:
            DateTime pre = now.AddMilliseconds(-1500d);
            while (this.ManualDragQueue.Count > 0 && this.ManualDragQueue.Peek().Item1 < pre)
                this.ManualDragQueue.Dequeue();
            this.ManualDragQueue.Enqueue(new Tuple<DateTime, Point>(now, point));
        }
        /// <summary>
        /// Dokončení ručního pohybu, určení vektoru rychlosti a nastartování inerciálního pohybu
        /// </summary>
        /// <param name="point"></param>
        internal void ManualDragEnd(PointF point)
        {
            Vector? vector = ManualDragGetMoveVector(this.ManualDragQueue, DateTime.Now);
            if (vector.HasValue && !vector.Value.IsEmpty)
                this.SetMoveVector(point, vector.Value, this.Acceleration);
            this.ManualDragQueue = null;
        }
        /// <summary>
        /// Pokusí se určit vektor pohybu z dat o času a souřadnici známého pohybu
        /// </summary>
        /// <param name="queue"></param>
        /// <returns></returns>
        private Vector? ManualDragGetMoveVector(Queue<Tuple<DateTime, Point>> queue, DateTime end)
        {
            List<Tuple<DateTime, Point>> list = queue.ToList();
            return this.ManualDragGetVector1(list, end);
        }
        /// <summary>
        /// Určí vektor pohybu jako průměr z času T-800ms až T-150ms, kde T je čas posledního záznamu.
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        private Vector? ManualDragGetVector1(List<Tuple<DateTime, Point>> list, DateTime end)
        {
            int cnt = list.Count;
            if (cnt < 5) return null;                                // Ze čtyř (a méně) záznamů nic neodhadnu.
            
            // Pokud v posledním čase 0.170 sec se nezměnila poloha (nejsou registrované pohyby, nebo jsou pod 15 pixelů), pak se nejedná o rozjezd k pohybu:
            Vector? latest = ManualDragGetVector(ManualDragGetListbyTime(list, end.Subtract(TimeSpan.FromMilliseconds(170d)), end));
            if (!latest.HasValue || (latest.HasValue && latest.Value.Length < 15d)) return null;

            // Vezmu záznamy, které se odehrály v intervalu T-800ms až T-150ms (kde T je čas posledního záznamu), 
            // tyto záznamy se vztahují k časovému úseku, kdy 
            double milisec = ManualDragGetTimeSpan(list).Value.TotalMilliseconds;       // Doba celého pohybu v milisekundách
            double msb = 0.8d * milisec;
            double mse = 0.1d * milisec;
            msb = (msb < 100d ? 100d : (msb > 950d ? 950d : msb));
            mse = (mse < 25d ? 25d : (mse > 150d ? 150d : mse));
            DateTime first = end.AddMilliseconds(-msb);
            DateTime last = end.AddMilliseconds(-mse);

            List<Tuple<DateTime, Point>> filt = ManualDragGetListbyTime(list, first, last);
            cnt = filt.Count;
            if (cnt < 2) return null;                                // Z jednoho záznamu nic neodhadnu.

            // Z těchto záznamů určím dráhu (od počátku do konce) a čas, a z toho všeho vyrobím vektor s rychlostí v pixelech za 1 sec:
            Vector vector = ManualDragGetVector(filt).Value;         // Vektor pohybu
            if (vector.Length < 5d) return null;                     // Nic se výrazně nepohnulo
            TimeSpan time = ManualDragGetTimeSpan(filt).Value;       // Doba pohybu
            if (time.TotalMilliseconds <= 1d) return null;           // Doba nestojí za řeč
            vector.Length = vector.Length / time.TotalSeconds;       // Délka vektoru má odpovídat počtu pixelů za jednu sekundu
            return vector;
        }
        /// <summary>
        /// Vrátí seznam prvků, jejichž čas je OD-DO (včetně obou krajů)
        /// </summary>
        /// <param name="list"></param>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <returns></returns>
        private static List<Tuple<DateTime, Point>> ManualDragGetListbyTime(List<Tuple<DateTime, Point>> list, DateTime from, DateTime to)
        {
            return list.Where(t => t.Item1 >= from && t.Item1 <= to).ToList();
        }
        /// <summary>
        /// Metoda vrátí časový úsek od prvního do posledního záznamu
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        private static TimeSpan? ManualDragGetTimeSpan(List<Tuple<DateTime, Point>> list)
        {
            int cnt = list.Count;
            if (cnt == 0) return null;
            DateTime dt1 = list[0].Item1;
            DateTime dt2 = list[cnt - 1].Item1;
            return dt2 - dt1;
        }
        /// <summary>
        /// Metoda vrátí vektor pohybu od prvního do posledního záznamu
        /// </summary>
        /// <param name="list"></param>
        /// <returns></returns>
        private static Vector? ManualDragGetVector(List<Tuple<DateTime, Point>> list)
        {
            int cnt = list.Count;
            if (cnt == 0) return null;
            Point pt1 = list[0].Item2;
            Point pt2 = list[cnt - 1].Item2;
            return new Vector((PointD)pt2.Sub(pt1));
        }
        protected Queue<Tuple<DateTime, Point>> ManualDragQueue;
        #endregion
    }
    /// <summary>
    /// Předpis pro handler události MoveEvent třídy InertialMovement
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    public delegate void InertialMovementEventHandler(object sender, InertialMovementArgs args);
    /// <summary>
    /// Data pro handler události MoveEvent třídy InertialMovement. Obsahuje cílový bod pohybu Target.
    /// </summary>
    public class InertialMovementArgs : EventArgs
    {
        public InertialMovementArgs(Point target)
        {
            this.Target = target;
        }
        /// <summary>
        /// Požadovaný cílový bod
        /// </summary>
        public Point Target { get; private set; }
        /// <summary>
        /// Je možno nastavit na true, a tím zastavit pohyb
        /// </summary>
        public bool Cancel { get; set; }
    }
    #endregion

    /// <summary>
    /// Jeden vizuální button
    /// </summary>
    public class CommandButton
    {
        // public class CommandButton
        public string Name { get; set; }
        public string GroupName { get; set; }
        public Image ImageStandard { get; set; }
        public Image ImageDisabled { get; set; }
        public Image ImageHotMouse { get; set; }
        public Image ImagePressed { get; set; }
        public Image ImageFocused { get; set; }
        /// <summary>Barva prvku ve stavu Standard</summary>
        public virtual Color? BackColorStandard { get; set; }
        /// <summary>Barva prvku ve stavu Disabled</summary>
        public virtual Color? BackColorDisabled { get; set; }
        /// <summary>Barva prvku ve stavu HotMouse</summary>
        public virtual Color? BackColorHotMouse { get; set; }
        /// <summary>Barva prvku ve stavu Focused</summary>
        public virtual Color? BackColorFocused { get; set; }
        /// <summary>Barva prvku ve stavu Pressed</summary>
        public virtual Color? BackColorPressed { get; set; }
        public string Text { get; set; }
        /// <summary>Titulek tooltipu</summary>
        public string ToolTipTitle { get; set; }
        /// <summary>Text tooltipu</summary>
        public string ToolTipText { get; set; }
    }
}
